From 6dab4036d6f4de64b4d5be24b6176966bfd314af Mon Sep 17 00:00:00 2001 From: Sreyanth Date: Tue, 24 Oct 2017 23:00:08 +0530 Subject: [PATCH] Initial commit --- .babelrc | 3 + .editorconfig | 11 + .eslintrc | 8 + .flowconfig | 0 .gitattributes | 1 + .gitignore | 6 + .travis.yml | 10 + AUTHORS.md | 5 + CHANGELOG.md | 18 + LICENSE.txt | 19 + README.md | 99 +++ examples/accounts.js | 25 + examples/applications.js | 28 + examples/calls.js | 15 + examples/endpoints.js | 37 ++ examples/numbers.js | 26 + examples/pricings.js | 10 + examples/recordings.js | 10 + examples/subaccounts.js | 30 + gulpfile.js | 96 +++ lib/base.js | 144 +++++ lib/resources/accounts.js | 250 ++++++++ lib/resources/applications.js | 164 +++++ lib/resources/call.js | 584 ++++++++++++++++++ lib/resources/conferences.js | 662 ++++++++++++++++++++ lib/resources/endpoints.js | 157 +++++ lib/resources/messages.js | 109 ++++ lib/resources/numbers.js | 241 ++++++++ lib/resources/pricings.js | 66 ++ lib/resources/recordings.js | 88 +++ lib/rest/client-test.js | 52 ++ lib/rest/client.js | 65 ++ lib/rest/request-test.js | 1074 +++++++++++++++++++++++++++++++++ lib/rest/request.js | 63 ++ lib/rest/utils.js | 46 ++ lib/utils/common.js | 69 +++ lib/utils/exceptions.js | 6 + lib/utils/plivoxml.js | 457 ++++++++++++++ lib/utils/security.js | 31 + lib/version.js | 0 package.json | 70 +++ test/accounts.js | 92 +++ test/applications.js | 69 +++ test/calls.js | 292 +++++++++ test/conferences.js | 258 ++++++++ test/endpoints.js | 75 +++ test/messages.js | 52 ++ test/numbers.js | 93 +++ test/pricings.js | 23 + test/recordings.js | 54 ++ test/utils.js | 40 ++ test/xml.js | 27 + 52 files changed, 5930 insertions(+) create mode 100644 .babelrc create mode 100644 .editorconfig create mode 100644 .eslintrc create mode 100644 .flowconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 AUTHORS.md create mode 100644 CHANGELOG.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 examples/accounts.js create mode 100644 examples/applications.js create mode 100644 examples/calls.js create mode 100644 examples/endpoints.js create mode 100644 examples/numbers.js create mode 100644 examples/pricings.js create mode 100644 examples/recordings.js create mode 100644 examples/subaccounts.js create mode 100644 gulpfile.js create mode 100644 lib/base.js create mode 100644 lib/resources/accounts.js create mode 100644 lib/resources/applications.js create mode 100644 lib/resources/call.js create mode 100644 lib/resources/conferences.js create mode 100644 lib/resources/endpoints.js create mode 100644 lib/resources/messages.js create mode 100644 lib/resources/numbers.js create mode 100644 lib/resources/pricings.js create mode 100644 lib/resources/recordings.js create mode 100644 lib/rest/client-test.js create mode 100644 lib/rest/client.js create mode 100644 lib/rest/request-test.js create mode 100644 lib/rest/request.js create mode 100644 lib/rest/utils.js create mode 100644 lib/utils/common.js create mode 100644 lib/utils/exceptions.js create mode 100644 lib/utils/plivoxml.js create mode 100644 lib/utils/security.js create mode 100644 lib/version.js create mode 100644 package.json create mode 100644 test/accounts.js create mode 100644 test/applications.js create mode 100644 test/calls.js create mode 100644 test/conferences.js create mode 100644 test/endpoints.js create mode 100644 test/messages.js create mode 100644 test/numbers.js create mode 100644 test/pricings.js create mode 100644 test/recordings.js create mode 100644 test/utils.js create mode 100644 test/xml.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..478397bf --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015", "flow"] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..beffa308 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..332bed04 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,8 @@ +{ + "extends": [ + "plugin:flowtype/recommended" + ], + "plugins": [ + "flowtype" + ] +} diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 00000000..e69de29b diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..176a458f --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..385e5082 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +docs/ +.DS_Store +.idea/ +node_modules +coverage +dist diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..a40bb382 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +service_name: travis-ci +language: node_js +node_js: + - "node" + - "lts/*" + - "8" + - "7" + - "6" + - "5" + - "4" diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 00000000..42a3212a --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,5 @@ +# Authors +- [Aviral Dasgupta](http://www.aviraldg.com) ([@aviraldg](http://github.com/aviraldg)) +- Ketan Tada [@ketantada](https://github.com/ketantada) +- Abhishek [@abhishek-plivo](https://github.com/abhishek-plivo) +- [Sreyantha Chary](https://sreyanth.com?ref=github/plivo-python) ([@sreyanth](https://github.com/sreyanth)) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..8e02bcec --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# Change Log + +## [0.4.0](Integrate patch from rob.polak@gmail.com and fix travis test)(2016-09-09) + - Added an entry to the .gitignore for WebStorm IDE's, this prevents un-needed files from being checked in. + - Updated Request to version 2.71.0 + - Some logic for how JSON serialazation has changed, I have updated the request method to reflect that changed + - Updated xmlBuilder to 8.0.0 + - Slight update to syntax for creating a new xml doc : xmlBuilder.begin().ele(this.element); + - Had to update Utility.areEqual to reflect a change in how xmlBuild stores values of child elements + - Updated nock to version 8.0.0, + - Updated Mocha to 2.4.5 + - Fix Travis tests (node v6 and v4) + +## Other changes +- 2015-01-14 Adds support for PhoneNumber API +- 2013-09-25 Added relayDTMF to and async to +- 2013-07-23 addRecord Response mandatory parameter 'body' dropped +- 2013-02-23 pricing API added diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..71ca9114 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (C) 2017, Plivo Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..a08cd83e --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# plivo-node +The Node.js SDK makes it simpler to integrate communications into your Node.js applications using the Plivo REST API. Using the SDK, you will be able to make voice calls, send SMS and generate Plivo XML to control your call flows. + +## Installation +Install the SDK using [npm](https://www.npmjs.com/package/plivo) + + $ npm install plivo@beta + +If you have the `0.4.1` version (a.k.a legacy) already installed, you may have to first uninstall it before installing the new version. + +## Getting started + +### Authentication +To make the API requests, you need to create a `Client` and provide it with authentication credentials (which can be found at [https://manage.plivo.com/dashboard/](https://manage.plivo.com/dashboard/)). + +We recommend that you store your credentials in the `PLIVO_AUTH_ID` and the `PLIVO_AUTH_TOKEN` environment variables, so as to avoid the possibility of accidentally committing them to source control. If you do this, you can initialise the client with no arguments and it will automatically fetch them from the environment variables: + +```javascript +let plivo = require('plivo'); +let client = new plivo.Client(); +``` +Alternatively, you can specifiy the authentication credentials while initializing the `Client`. + +```javascript +let plivo = require('plivo'); +let client = new plivo.Client('your_auth_id', 'your_auth_token'); +``` + +### The basics +The SDK uses consistent interfaces to create, retrieve, update, delete and list resources. The pattern followed is as follows: + +```javascript +client.resources.create(name,params); // Create +client.resources.get(id); // Get +client.resources.update(params); // Update +client.resources.delete(id); // Delete +client.resources.list({limit:5,offset:0}); // List all resources, max 20 at a time +``` + +Also, using `client.resources.list()` would list the first 20 resources by default (which is the first page, with `limit` as 20, and `offset` as 0). To get more, you will have to use `limit` and `offset` to get the second page of resources. + +## Examples + +### Send a message + +```javascript +let plivo = require('plivo'); +let client = new plivo.Client(); + +client.messages.create( + 'your_source_number', + 'your_destination_number', + 'Hello, world!' +).then(function(message_created) { + console.log(message_created) +}); + +``` + +### Make a call + +```javascript +let plivo = require('plivo'); +let client = new plivo.Client(); + +client.calls.create( + 'your_source_number', + 'your_destination_number', + 'http://answer.url' +).then(function(call_created) { + console.log(call_created) +}); + +``` + +### Generate Plivo XML + +```javascript +let plivo = require('plivo'); +let response = new plivo.Response(); +let speak_body = "Hello, world!"; + +response.addSpeak(speak_body); +console.log(response.toXML()); +``` + +This generates the following XML: + +```xml + + Hello, world! + +``` + +### More examples +Refer to the [Plivo API Reference](https://api-reference.plivo.com/latest/node/introduction/overview) for more examples. Also refer to the [guide to setting up dev environment](https://developers.plivo.com/getting-started/setting-up-dev-environment/) on [Plivo Developers Portal](https://developers.plivo.com) to setup an Express server & use it to test out your integration in under 5 minutes. + +## Reporting issues +Report any feedback or problems with this beta version by [opening an issue on Github](https://github.com/plivo/plivo-node/issues). diff --git a/examples/accounts.js b/examples/accounts.js new file mode 100644 index 00000000..e722ff23 --- /dev/null +++ b/examples/accounts.js @@ -0,0 +1,25 @@ +var Plivo = require('../dist/rest/client.js'); +var client = new Plivo.Client(); + +var name; +//================== Accounts ============= +client.accounts.get() + .then(function(account) { + console.log("\n============ Account Detail ===========\n", account) + name = account.name; + return account.update({ + name: 'newName' + }) + }) + .then(function(account) { + console.log("\n============ updated ===========\n", account) + return account.update({ + name + }) + }) + .then(function(account) { + console.log("\n============ original ===========\n", account) + }) + .catch(function(response) { + console.log("\n============ Error :: ===========\n", response, response.message); + }); diff --git a/examples/applications.js b/examples/applications.js new file mode 100644 index 00000000..419cd974 --- /dev/null +++ b/examples/applications.js @@ -0,0 +1,28 @@ +var Plivo = require('../dist/rest/client.js'); +var client = new Plivo.Client(); + +client.applications.create("http://google.com/", "MyNewApplication" + Math.random(1000)) + .then(function(application) { + console.log("\n============ created ===========\n", application) + return client.applications.update(application.id, { + answer_url: 'http://google1.com/' + }); + }) + .then(function(application) { + console.log("\n============ updated ===========\n", application) + return client.applications.get(application.id); + }) + .then(function(application){ + console.log("\n============ list with id ===========\n", application) + return application.delete(); + }) + .then(function(result){ + console.log("\n============ deleted ===========\n", result) + return client.applications.list() + }) + .then(function(applications){ + console.log("\n============ list all ===========\n", applications) + }) + .catch(function(response) { + console.log("\n============ Error :: ===========\n", response); + }); diff --git a/examples/calls.js b/examples/calls.js new file mode 100644 index 00000000..28eb5ec0 --- /dev/null +++ b/examples/calls.js @@ -0,0 +1,15 @@ +var Plivo = require('../dist/rest/client.js'); +var client = new Plivo.Client(); + + +client.calls.create('', '', 'http://localhost/') + .then(function(call) { + console.log("\n============ make call ===========\n", call) + return client.calls.hangup(call.id) + }) + .then(function(call){ + console.log("\n============ hangup ===========\n", call) + }) + .catch(function(response) { + console.log("\n============ Error :: ===========\n", response); + }); diff --git a/examples/endpoints.js b/examples/endpoints.js new file mode 100644 index 00000000..560be992 --- /dev/null +++ b/examples/endpoints.js @@ -0,0 +1,37 @@ +var Plivo = require('../dist/rest/client.js'); +var client = new Plivo.Client(); + +var create = { + username: "thisistestendpoint", + password: "thisistestpassword", + alias: "this is test alias" +}; + +var update = { + password: "hi new password", + alias: "hi new alias" +}; + +client.endpoints.create(create.username, create.password, create.alias) + .then(function(endpoint) { + console.log("\n============ created ===========\n", endpoint) + return client.endpoints.update(endpoint.id, update.username, update.password, update.alias); + }) + .then(function(endpoint) { + console.log("\n============ updated ===========\n", endpoint) + return client.endpoints.get(endpoint.id); + }) + .then(function(endpoint){ + console.log("\n============ list with id ===========\n", endpoint) + return endpoint.delete(); + }) + .then(function(result){ + console.log("\n============ deleted ===========\n", result) + return client.endpoints.list() + }) + .then(function(endpoints){ + console.log("\n============ list all ===========\n", endpoints) + }) + .catch(function(response) { + console.log("\n============ Error :: ===========\n", response); + }); diff --git a/examples/numbers.js b/examples/numbers.js new file mode 100644 index 00000000..e6df50e3 --- /dev/null +++ b/examples/numbers.js @@ -0,0 +1,26 @@ +var Plivo = require('../dist/rest/client.js'); +var client = new Plivo.Client(); + +client.numbers.search('US') + .then(function(numbers) { + console.log("\n============ search ===========\n", numbers) + return client.numbers.list() + }) + .then(function(numbers){ + console.log("\n============ all ===========\n", numbers) + return client.numbers.get(numbers[0].id) + }) + .then(function(number){ + console.log("\n============ get ===========\n", number) + return number.update(null, null, 'new alias') + }) + .then(function(number){ + console.log("\n============ update ===========\n", number) + return client.numbers.get(number.id) + }) + .then(function(number){ + console.log("\n============ get update ===========\n", number) + }) + .catch(function(response) { + console.log("\n============ Error :: ===========\n", response); + }); diff --git a/examples/pricings.js b/examples/pricings.js new file mode 100644 index 00000000..641ffb1b --- /dev/null +++ b/examples/pricings.js @@ -0,0 +1,10 @@ +var Plivo = require('../dist/rest/client.js'); +var client = new Plivo.Client(); + +client.pricings.get('US') + .then(function(price) { + console.log("\n============ get ===========\n", price) + }) + .catch(function(response) { + console.log("\n============ Error :: ===========\n", response); + }); diff --git a/examples/recordings.js b/examples/recordings.js new file mode 100644 index 00000000..b7f08e56 --- /dev/null +++ b/examples/recordings.js @@ -0,0 +1,10 @@ +var Plivo = require('../dist/rest/client.js'); +var client = new Plivo.Client(); + +client.recordings.list() + .then(function(recordings) { + console.log("\n============ list ===========\n", recordings) + }) + .catch(function(response) { + console.log("\n============ Error :: ===========\n", response); + }); diff --git a/examples/subaccounts.js b/examples/subaccounts.js new file mode 100644 index 00000000..34d0d160 --- /dev/null +++ b/examples/subaccounts.js @@ -0,0 +1,30 @@ +var Plivo = require('../dist/rest/client.js'); +var client = new Plivo.Client(); + +client.subAccounts.create(new Date().getTime().toString()) + .then(function(response) { + console.log("\n============ Sub Account Detail ===========\n", response) + return client.subAccounts.get(response.auth_id) + }) + .then(function(response) { + console.log("\n============ Sub Account Detail ===========\n", response) + return response.update(new Date().getTime().toString(), true) + }) + .then(function(response) { + console.log("\n============ Sub Account Detail ===========\n", response) + return client.subAccounts.get(response.auth_id) + }) + .then(function(response) { + console.log("\n============ updated ===========\n", response) + return response.delete() + }) + .then(function(response) { + console.log("\n============ deleted ===========\n", response) + return client.subAccounts.list() + }) + .then(function(response) { + console.log("\n============ All Subaccounts ===========\n", response) + }) + .catch(function(response) { + console.log("\n============ Error :: ===========\n", response, response.message); + }); diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 00000000..dd148507 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,96 @@ +var path = require('path'); +var gulp = require('gulp'); +var eslint = require('gulp-eslint'); +var excludeGitignore = require('gulp-exclude-gitignore'); +var mocha = require('gulp-mocha'); +var istanbul = require('gulp-babel-istanbul'); +var nsp = require('gulp-nsp'); +var plumber = require('gulp-plumber'); +var coveralls = require('gulp-coveralls'); +var babel = require('gulp-babel'); +var del = require('del'); +var isparta = require('isparta'); + +// Initialize the babel transpiler so ES2015 files gets compiled +// when they're loaded +require('babel-register'); + +gulp.task('static', function () { + return gulp.src(['lib/**/*.js', '!lib/rest/request-test.js', '!lib/rest/client-test.js']) + .pipe(excludeGitignore()) + .pipe(eslint({ + rules: { + 'symbol-description': 0, + 'no-negated-condition': 0, + 'new-cap': 0, + 'no-use-before-define': 0, + 'camelcase': 0 + } + })) + .pipe(eslint.format()) + .pipe(eslint.failAfterError()); +}); + +gulp.task('nsp', function (cb) { + nsp({package: path.resolve('package.json')}, cb); +}); + +gulp.task('pre-test', function () { + return gulp.src('lib/**/*.js') + .pipe(excludeGitignore()) + .pipe(istanbul({ + includeUntested: false, + instrumenter: isparta.Instrumenter + })) + .pipe(istanbul.hookRequire()); +}); + +gulp.task('test', ['pre-test'], function (cb) { + var mochaErr; + + gulp.src('test/**/*.js') + .pipe(plumber()) + .pipe(mocha({reporter: 'spec'})) + .on('error', function (err) { + mochaErr = err; + }) + .pipe(istanbul.writeReports()) + .on('end', function () { + cb(mochaErr); + }); +}); + +gulp.task('watch', function () { + gulp.watch(['lib/**/*.js', 'test/**'], ['test']); +}); + +gulp.task('coveralls', ['test'], function () { + if (!process.env.CI) { + return; + } + + return gulp.src(path.join(__dirname, 'coverage/lcov.info')) + .pipe(coveralls()); +}); + +gulp.task('babel', ['clean'], function () { + return gulp.src('lib/**/*.js') + .pipe(babel()) + .pipe(gulp.dest('dist')); +}); + +gulp.task('lintFix', function () { + return gulp.src('lib/**/*.js') + .pipe(excludeGitignore()) + .pipe(eslint({ + fix: true + })) + .pipe(gulp.dest('lib')); +}); + +gulp.task('clean', function () { + return del('dist'); +}); + +gulp.task('prepublish', ['nsp', 'babel']); +gulp.task('default', ['static', 'test', 'coveralls']); diff --git a/lib/base.js b/lib/base.js new file mode 100644 index 00000000..9a246d33 --- /dev/null +++ b/lib/base.js @@ -0,0 +1,144 @@ +import {extend} from './utils/common.js'; + +let actionKey = Symbol('api action'); +let klassKey = Symbol('constructor'); +let idKey = Symbol('id filed'); +let clientKey = Symbol('make api call'); + +export class PlivoGenericResponse { + constructor(params, idString) { + params = params || {}; + if (typeof idString !== 'undefined' && (idString in params)) { + this.id = params[idString]; + } else if ('request_uuid' in params) { + this.id = params.request_uuid; + } + extend(this, params); + } +} + +export class PlivoResource { + constructor(action, klass, idField, request) { + this[actionKey] = action; + this[klassKey] = klass; + this[idKey] = idField; + this[clientKey] = request; + } + + update(params, id) { + let client = this[clientKey]; + let action = this[actionKey]; + let that = this; + id = typeof id !== 'undefined' ? id : that.id; + + return new Promise((resolve, reject) => { + client('POST', action + id + '/', params) + .then(response => { + extend(that, response.body); + extend(that, params); + resolve(that); + }) + .catch(error => { + reject(error); + }); + }); + } + + delete() { + let client = this[clientKey]; + let action = this[actionKey]; + let id = this.id; + + return new Promise((resolve, reject) => { + client('DELETE', action + id + '/') + .then(() => { + resolve(true); + }) + .catch(error => { + reject(error); + }); + }); + } + + executeAction(task = '', method = 'GET', params = {}, action) { + let client = this[clientKey]; + action = action == null ? this[actionKey] : action; + let idField = this[idKey]; + + return new Promise((resolve, reject) => { + client(method, action + task, params) + .then(response => { + resolve(new PlivoGenericResponse(response.body, idField)); + }) + .catch(error => { + reject(error); + }); + }); + } +} + +export class PlivoResourceInterface { + constructor(action, klass, idField, request) { + this[actionKey] = action; + this[klassKey] = klass; + this[idKey] = idField; + this[clientKey] = request; + } + + get(id, params = {}) { + let client = this[clientKey]; + let action = this[actionKey]; + let Klass = this[klassKey]; + + return new Promise((resolve, reject) => { + if (action !== '' && !id) { + reject(new Error(this[idKey] + ' must be set')); + } + + client('GET', action + (id ? id + '/' : ''), params) + .then(response => { + resolve(new Klass(client, response.body)); + }) + .catch(error => { + reject(error); + }); + }); + } + list(params) { + let client = this[clientKey]; + let action = this[actionKey]; + let Klass = this[klassKey]; + + return new Promise((resolve, reject) => { + client('GET', action, params) + .then(response => { + let objects = []; + response.body.objects.forEach(item => { + objects.push(new Klass(client, item)); + }); + resolve(objects); + }) + .catch(error => { + reject(error); + }); + }); + } + + create(params) { + let client = this[clientKey]; + let idField = this[idKey]; + let action = this[actionKey] + (this.id ? this.id + '/' : ''); + + return new Promise((resolve, reject) => { + client('POST', action, params) + .then(response => { + resolve(new PlivoGenericResponse(response.body, idField)); + }) + .catch(error => { + reject(error); + }); + }); + } + +} + diff --git a/lib/resources/accounts.js b/lib/resources/accounts.js new file mode 100644 index 00000000..a7b72d1a --- /dev/null +++ b/lib/resources/accounts.js @@ -0,0 +1,250 @@ +import {extend, validate} from '../utils/common.js'; +import {PlivoResource, PlivoResourceInterface} from '../base'; + +const clientKey = Symbol(); +const action = ''; +const idField = 'authId'; + +/** + * Represents a SubAccount + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class Subaccount extends PlivoResource { + constructor(client, data = {}) { + super('Subaccount/', Subaccount, idField, client); + + if (idField in data) { + this.id = data[idField]; + } + + extend(this, data); + } + +/** + * update subaccount + * @method + * @param {string} name - name of subaccount + * @param {boolean} enabled - make account enable or disable + * @promise {Subaccount} return object of subaccount + * @fail {Error} return Error + */ + update(name, enabled) { + let params = {}; + + let errors = validate([ + {field: 'name', value: name, validators: ['isRequired', 'isString']} + ]); + + if (errors) { + return errors; + } + + params.name = name; + + if (typeof enabled === 'boolean') { + params.enabled = enabled.toString(); + } + + return super.update(params); + } + +/** + * delete subaccount + * @method + * @promise {boolean} return true if subaccount deleted + * @fail {Error} return Error + */ + delete() { + return super.delete(); + } + +} + +/** + * Represents a Subaccount Interface + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class SubaccountInterface extends PlivoResourceInterface { + + constructor(client, data = {}) { + super('Subaccount/', Subaccount, idField, client); + extend(this, data); + this[clientKey] = client; + } + +/** + * get subaccount by id + * @method + * @param {string} id - id of subaccount + * @promise {Subaccount} return object of subaccount + * @fail {Error} return Error + */ + get(id) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return super.get(id); + } + +/** + * create subaccount + * @method + * @param {string} name - name of subaccount + * @param {boolean} enabled - enable or disable subaccount + * @promise {PlivoGenericResponse} return object of PlivoGenericObject + * @fail {Error} return Error + */ + create(name, enabled) { + let params = {}; + + let errors = validate([ + {field: 'name', value: name, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + + params.name = name; + + if (typeof enabled === 'boolean') { + params.enabled = enabled; + } + + return super.create(params); + } + +/** + * update subaccount + * @method + * @param {id} id - id of subaccount + * @param {string} name - name of subaccount + * @param {boolean} enabled - make account enable or disable + * @promise {Subaccount} return object of subaccount + * @fail {Error} return Error + */ + update(id, name, enabled) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Subaccount(this[clientKey], { + id: id + }).update(name, enabled); + } + +/** + * delete subaccount + * @method + * @param {id} id - id of subaccount + * @promise {boolean} return true if subaccount deleted + * @fail {Error} return Error + */ + delete(id) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Subaccount(this[clientKey], { + id: id + }).delete(); + } +} + +/** + * Represents a Account + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class Account extends PlivoResource { + constructor(client, data = {}) { + super(action, Account, idField, client); + extend(this, data); + + if (idField in data) { + this.id = data[idField]; + } + + this[clientKey] = client; + } + +/** + * get account detail + * @method + * @promise {PlivoGenericResponse} return PlivoGenericResponse object + * @fail {Error} return Error + */ + get() { + return new AccountInterface(this[clientKey]) + .get(); + } + +/** + * update account detail + * @method + * @param {object} params - parameters + * @param {string} [params.name] - name of account + * @param {string} [params.city] - city of account + * @param {string} [params.address] - address of account + * @promise {Account} return Account object + * @fail {Error} return Error + */ + update(params) { + return super.update(params, ''); + } +} + +/** + * Represents a Account Interface + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class AccountInterface extends PlivoResourceInterface { + constructor(client, data = {}) { + super(action, Account, idField, client); + extend(this, data); + + this[clientKey] = client; + } + +/** + * get account detail + * @method + * @promise {PlivoGenericResponse} return PlivoGenericResponse object + * @fail {Error} return Error + */ + get() { + return super.get(); + } + +/** + * update account detail + * @method + * @param {object} params - parameters + * @param {string} [params.name] - name of account + * @param {string} [params.city] - city of account + * @param {string} [params.address] - address of account + * @promise {Account} return Account object + * @fail {Error} return Error + */ + update(params) { + return new Account(this[clientKey]) + .update(params); + } +} diff --git a/lib/resources/applications.js b/lib/resources/applications.js new file mode 100644 index 00000000..afcbfa75 --- /dev/null +++ b/lib/resources/applications.js @@ -0,0 +1,164 @@ +import {extend, validate} from '../utils/common.js'; +import {PlivoResource, PlivoResourceInterface} from '../base'; + +const clientKey = Symbol(); +const action = 'Application/'; +const idField = 'appId'; + +/** + * Represents a Application + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class Application extends PlivoResource { + constructor(client, data = {}) { + super(action, Application, idField, client); + + if (idField in data) { + this.id = data[idField]; + } + + extend(this, data); + } + +/** + * update application + * @method + * @param {object} params - to update application + * @param {string} [params.answerUrl] The URL invoked by Plivo when a call executes this application. + * @param {string} [params.answerMethod] The method used to call the answer_url. Defaults to POST. + * @param {string} [params.hangupUrl] The URL that is notified by Plivo when the call hangs up. + * @param {string} [params.hangupMethod] The method used to call the hangup_url. Defaults to POST + * @param {string} [params.fallbackAnswerUrl] Invoked by Plivo only if answer_url is unavailable or the XML response is invalid. Should contain a XML response. + * @param {string} [params.fallbackMethod] The method used to call the fallback_answer_url. Defaults to POST. + * @param {string} [params.messageUrl] The URL that is notified by Plivo when an inbound message is received. Defaults not set. + * @param {string} [params.messageMethod] The method used to call the message_url. Defaults to POST. + * @param {boolean} [params.defaultNumberApp] If set to true, associates all newly created Plivo numbers that have not specified an app_id, to this application. + * @param {boolean} [params.defaultEndpointApp] If set to true, associates all newly created Plivo endpoints that have not specified an app_id, to this application. + * @param {string} [params.subaccount] Id of the subaccount, in case only subaccount applications are needed. + * @promise {object} return {@link Application} object + * @fail {Error} return Error + */ + update(params) { + return super.update(params); + } + +/** + * delete application + * @method + * @promise {object} return true on success + * @fail {Error} return Error + */ + delete() { + return super.delete(); + } + +} +/** + * Represents a Application interface + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class ApplicationInterface extends PlivoResourceInterface { + + constructor(client, data = {}) { + super(action, Application, idField, client); + extend(this, data); + + this[clientKey] = client; + } + +/** + * get application by given id + * @method + * @param {string} id - id of application + * @promise {object} return {@link Application} object + * @fail {Error} return Error + */ + get(id) { + return super.get(id); + } + +/** + * create Application + * @method + * @param {string} appName - name of application + * @param {object} params - params to create application + * @param {string} [params.answerUrl] - answer url + * @param {string} [params.appName] The name of your application + * @param {string} [params.answerUrl] The URL invoked by Plivo when a call executes this application. + * @param {string} [params.answerMethod] The method used to call the answer_url. Defaults to POST. + * @param {string} [params.hangupUrl] The URL that is notified by Plivo when the call hangs up. + * @param {string} [params.hangupMethod] The method used to call the hangup_url. Defaults to POST + * @param {string} [params.fallbackAnswerUrl] Invoked by Plivo only if answer_url is unavailable or the XML response is invalid. Should contain a XML response. + * @param {string} [params.fallbackMethod] The method used to call the fallback_answer_url. Defaults to POST. + * @param {string} [params.messageUrl] The URL that is notified by Plivo when an inbound message is received. Defaults not set. + * @param {string} [params.messageMethod] The method used to call the message_url. Defaults to POST. + * @param {boolean} [params.defaultNumberApp] If set to true, associates all newly created Plivo numbers that have not specified an app_id, to this application. + * @param {boolean} [params.defaultEndpointApp] If set to true, associates all newly created Plivo endpoints that have not specified an app_id, to this application. + * @param {string} [params.subaccount] Id of the subaccount, in case only subaccount applications are needed. + * @promise {object} return {@link PlivoGenericResponse} object + * @fail {Error} return Error + */ + create(appName, params = {}) { + + let errors = validate([ + {field: 'app_name', value: appName, validators: ['isRequired', 'isString']} + ]); + + if (errors) { + return errors; + } + + params.app_name = appName; + + return super.create(params); + } + +/** + * update Application + * @method + * @param {string} id - id of application + * @param {object} params - to update application + * @param {string} [params.answerUrl] The URL invoked by Plivo when a call executes this application. + * @param {string} [params.answerMethod] The method used to call the answer_url. Defaults to POST. + * @param {string} [params.hangupUrl] The URL that is notified by Plivo when the call hangs up. + * @param {string} [params.hangupMethod] The method used to call the hangup_url. Defaults to POST + * @param {string} [params.fallbackAnswerUrl] Invoked by Plivo only if answer_url is unavailable or the XML response is invalid. Should contain a XML response. + * @param {string} [params.fallbackMethod] The method used to call the fallback_answer_url. Defaults to POST. + * @param {string} [params.messageUrl] The URL that is notified by Plivo when an inbound message is received. Defaults not set. + * @param {string} [params.messageMethod] The method used to call the message_url. Defaults to POST. + * @param {boolean} [params.defaultNumberApp] If set to true, associates all newly created Plivo numbers that have not specified an app_id, to this application. + * @param {boolean} [params.defaultEndpointApp] If set to true, associates all newly created Plivo endpoints that have not specified an app_id, to this application. + * @param {string} [params.subaccount] Id of the subaccount, in case only subaccount applications are needed. + * @promise {object} return {@link Application} object + * @fail {Error} return Error + */ + update(id, params) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Application(this[clientKey], { + id: id + }).update(params); + } + +/** + * delete Application + * @method + * @param {string} id - id of application + * @promise {object} return true on success + * @fail {Error} return Error + */ + delete(id) { + return new Application(this[clientKey], { + id: id + }).delete(); + } +} diff --git a/lib/resources/call.js b/lib/resources/call.js new file mode 100644 index 00000000..070b8e8f --- /dev/null +++ b/lib/resources/call.js @@ -0,0 +1,584 @@ + +import {extend, validate} from '../utils/common.js'; +import {PlivoResource, PlivoResourceInterface} from '../base'; +import * as _ from "lodash"; + +const clientKey = Symbol(); +const action = 'Call/'; +const idField = 'callUuid'; + +/** + * Represents a Call + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class Call extends PlivoResource { + constructor(client, data = {}) { + super(action, Call, idField, client); + + if (idField in data) { + this.id = data[idField]; + } + + extend(this, data); + this[clientKey] = client; + } + +/** + * hangup call + * @method + * @promise {Boolean} return true if call hung up + * @fail {Error} return Error + */ + hangup() { + return super.delete(); + } + +/** + * transfer call + * @method + * @param {object} params - optional params to transfer a call + * @param {string} [params.legs] aleg, bleg or both Defaults to aleg. aleg will transfer call_uuid ; bleg will transfer the bridged leg (if found) of call_uuid ; both will transfer call_uuid and bridged leg of call_uuid + * @param {string} [params.alegUrl] URL to transfer for aleg, if legs is aleg or both, then aleg_url has to be specified. + * @param {string} [params.alegMethod] HTTP method to invoke aleg_url. Defaults to POST. + * @param {string} [params.blegUrl] URL to transfer for bridged leg, if legs is bleg or both, then bleg_url has to be specified. + * @param {string} [params.blegMethod] HTTP method to invoke bleg_url. Defaults to POST. + * @promise {object} return call object + * @fail {Error} return Error + */ + transfer(params) { + return super.update(params); + } +/** + * record call + * @method + * @param {object} params - to record call + * @promise {object} return PlivoGenericResponse Object + * @fail {Error} return Error + */ + record(params) { + return this.startRecording(params); + } + +/** + * record call + * @method + * @param {object} params - to record call + * @promise {object} return PlivoGenericResponse Object + * @fail {Error} return Error + */ + startRecording(params) { + return super.executeAction(this.id + '/Record/', 'POST', params); + } +/** + * stop recording call + * @method + * @param {object} params - to stop recording call + * @promise {object} return PlivoGenericResponse Object + * @fail {Error} return Error + */ + stopRecording(params) { + return super.executeAction(this.id + '/Record/', 'DELETE', params); + } + +/** + * play music for call + * @method + * @param {string} url - url which contains audio to play for call + * @param {object} optionalParams - to stop recording call + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + playMusic(url, optionalParams) { + return this.startPlayingMusic(url, optionalParams); + } +/** + * play music for call + * @method + * @param {string} url - url which contains audio to play for call + * @param {object} optionalParams - to stop recording call + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + startPlayingMusic(urls, optionalParams) { + let params = optionalParams || {}; + params.urls = urls; + + let errors = validate([ + {field: 'urls', value: urls, validators: ['isRequired', 'isString']} + ]); + + if (errors) { + return errors; + } + return super.executeAction(this.id + '/Play/', 'POST', params); + } + +/** + * stop playing music for call + * @method + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + stopPlayingMusic() { + return super.executeAction(this.id + '/Play/', 'DELETE'); + } + +/** + * speak text for call + * @method + * @param {string} text - text to speak for call + * @param {object} optionalParams - to speak for call + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + speakText(text, optionalParams) { + return this.startSpeakingText(text, optionalParams); + } + +/** + * speak text for call + * @method + * @param {string} text - text to speak for call + * @param {object} optionalParams - to speak for call + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + startSpeakingText(text, optionalParams) { + let errors = validate([{field: 'text', value: text, validators: ['isRequired', 'isString']}]); + + if (errors) { + return errors; + } + + let params = optionalParams || {}; + params.text = text; + + return super.executeAction(this.id + '/Speak/', 'POST', params); + } + +/** + * stop speaking text for call + * @method + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + stopSpeakingText() { + return super.executeAction(this.id + '/Speak/', 'DELETE'); + } + +/** + * Send digits on a call + * @method + * @param {number} digits - digits to be send + * @param {object} optionalParams - to send digits for call + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + sendDigits(digits, optionalParams) { + let errors = validate([{field: 'digits', value: digits, validators: ['isRequired']}]); + + if (errors) { + return errors; + } + + let params = optionalParams || {}; + params.digits = digits; + + return super.executeAction(this.id + '/DTMF/', 'POST', params); + } + +/** + * Hangup a Call Request + * @method + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + cancel() { + return super.executeAction('Request/' + this.id + '/', 'DELETE', {}, ''); + } +} + +const liveCallInterfaceKey = Symbol('liveCallInterface'); + +/** + * Represents a Call Interface + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class CallInterface extends PlivoResourceInterface { + + constructor(client, data = {}) { + super(action, Call, idField, client); + extend(this, data); + + this[clientKey] = client; + this[liveCallInterfaceKey] = new LiveCallInterface(client); + } + +/** + * Get A Call Detail + * @method + * @param {string} id - call uuid to get information of. + * @promise {object} returns Call Object + * @fail {Error} returns Error + */ + get(id) { + let errors = validate([{field: 'id', value: id, validators: ['isRequired']}]); + + if (errors) { + return errors; + } + return super.get(id); + } + +/** + * Get All Call Detail + * @method + * @param {object} params - params to get all call details. + * @promise {object[]} returns list of Call Object + * @fail {Error} returns Error + */ + list(params) { + return super.list(params); + } + +/** + * Create a call + * @method + * @param {string} from - The phone number to be used as the caller id (with the country code).For e.g, a USA caller id number could be, 15677654321, with '1' for the country code. + * @param {string} to - The regular number(s) or sip endpoint(s) to call. Regular number must be prefixed with country code but without the + sign). For e.g, to dial a number in the USA, the number could be, 15677654321, with '1' for the country code. Multiple numbers can be sent by using a delimiter. For e.g. 15677654321<12077657621<12047657621. Sip endpoints must be prefixed with sip: E.g., sip:john1234@phone.plivo.com. To make bulk calls, the delimiter < is used. For example, 15677654321<15673464321 0(in seconds). + * @param {number} [params.hangupOnRing] Schedules the call for hangup at a specified time after the call starts ringing. Value should be an integer >= 0 (in seconds). + * @param {string} [params.machineDetection] Used to detect if the call has been answered by a machine. The valid values are true and hangup. + * @param {number} [params.machineDetectionTime] Time allotted to analyze if the call has been answered by a machine. It should be an integer >= 2000 and <= 10000 and the unit is ms. The default value is 5000 ms. + * @param {string} [params.machineDetectionUrl] A URL where machine detection parameters will be sent by Plivo. This parameter should be used to make machine detection asynchronous + * @param {string} [params.machineDetectionMethod] The HTTP method which will be used by Plivo to request the machine_detection_url. Defaults to POST. + * @param {string} [params.sipHeaders] List of SIP headers in the form of 'key=value' pairs, separated by commas. + * @param {number} [params.ringTimeout] Determines the time in seconds the call should ring. If the call is not answered within the ring_timeout value or the default value of 120s, it is canceled. + * @param {string} [params.parentCallUuid] The call_uuid of the first leg in an ongoing conference call. It is recommended to use this parameter in scenarios where a member who is already present in the conference intends to add new members by initiating outbound API calls. + * @param {boolean} [params.errorIfParentNotFound] if set to true and the parent_call_uuid cannot be found, the API request would return an error. If set to false, the outbound call API request will be executed even if the parent_call_uuid is not found. Defaults to false. + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + create(from, to, answerUrl, params = {}) { + let errors = validate([ + {field: 'from', value: from, validators: ['isRequired']}, + {field: 'to', value: to, validators: ['isRequired']}, + {field: 'answer_url', value: answerUrl, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + params.from = from; + params.to = _.isArray(to) ? _.join(to, '<') : to; + params.answer_url = answerUrl; + + return super.create(params); + } + +/** + * Hangup A Specific Call + * @method + * @param {string} callUUID - call uuid to hangup call + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + hangup(callUUID) { + let errors = validate([ + {field: 'call_uuid', value: callUUID, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Call(this[clientKey], { + id: callUUID + }).hangup(); + } +/** + * Transfer a Call + * @method + * @param {string} callUUID - call uuid to transfer call + * @param {object} params - optional params to transfer a call + * @param {string} [params.legs] aleg, bleg or both Defaults to aleg. aleg will transfer call_uuid ; bleg will transfer the bridged leg (if found) of call_uuid ; both will transfer call_uuid and bridged leg of call_uuid + * @param {string} [params.alegUrl] URL to transfer for aleg, if legs is aleg or both, then aleg_url has to be specified. + * @param {string} [params.alegMethod] HTTP method to invoke aleg_url. Defaults to POST. + * @param {string} [params.blegUrl] URL to transfer for bridged leg, if legs is bleg or both, then bleg_url has to be specified. + * @param {string} [params.blegMethod] HTTP method to invoke bleg_url. Defaults to POST. + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + transfer(callUUID, params) { + let errors = validate([ + {field: 'call_uuid', value: callUUID, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Call(this[clientKey], { + id: callUUID + }).transfer(params); + } + +/** + * Record a Call + * @method + * @param {string} callUUID - call uuid to record call + * @param {object} optionalParams - optional params to record a call + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + record(callUUID, optionalParams) { + let errors = validate([ + {field: 'call_uuid', value: callUUID, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Call(this[clientKey], { + id: callUUID + }).record(optionalParams); + } + +/** + * Stop Recording a Call + * @method + * @param {string} callUUID - call uuid to stop recording a call + * @param {object} optionalParams - optional params to stop recording a call + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + stopRecording(callUUID, optionalParams) { + let errors = validate([ + {field: 'call_uuid', value: callUUID, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Call(this[clientKey], { + id: callUUID + }).stopRecording(optionalParams); + } + +/** + * Play a music file + * @method + * @param {string} callUUID - call uuid to play music file + * @param {string} url - A single URL or a list of comma separated URLs linking to an mp3 or wav file. + * @param {object} optionalParams - optional params to play music file. + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + playMusic(callUUID, urls, optionalParams) { + let errors = validate([ + {field: 'call_uuid', value: callUUID, validators: ['isRequired']}, + {field: 'urls', value: urls, validators: ['isRequired', 'isString']} + ]); + + if (errors) { + return errors; + } + return new Call(this[clientKey], { + id: callUUID + }).playMusic(urls, optionalParams); + } + +/** + * Stop Playing a music file + * @method + * @param {string} callUUID - call uuid to stop plaing music file + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + stopPlayingMusic(callUUID) { + let errors = validate([ + {field: 'call_uuid', value: callUUID, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Call(this[clientKey], { + id: callUUID + }).stopPlayingMusic(); + } + +/** + * Speak text during a call + * @method + * @param {string} callUUID - call uuid to speak text during a call + * @param {string} text - text to be played. + * @param {object} optionalParams - optional params to speak text during a call + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + speakText(callUUID, text, optionalParams) { + let errors = validate([ + {field: 'call_uuid', value: callUUID, validators: ['isRequired']}, + {field: 'text', value: text, validators: ['isRequired', 'isString']} + ]); + + if (errors) { + return errors; + } + return new Call(this[clientKey], { + id: callUUID + }).speakText(text, optionalParams); + } + +/** + * Stop Speaking text during a call + * @method + * @param {string} callUUID - call uuid to stop speaking text during a call + * @param {object} optionalParams - optional params to stop speaking text during a call + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + stopSpeakingText(callUUID) { + let errors = validate([ + {field: 'call_uuid', value: callUUID, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Call(this[clientKey], { + id: callUUID + }).stopSpeakingText(); + } + +/** + * Send digits on a call + * @method + * @param {string} callUUID - call uuid to send digits on a call + * @param {number} digits - digits to be send + * @param {object} optionalParams - optional params to send digits + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + sendDigits(callUUID, digits, optionalParams) { + let errors = validate([ + {field: 'call_uuid', value: callUUID, validators: ['isRequired']}, + {field: 'digits', value: digits, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Call(this[clientKey], { + id: callUUID + }).sendDigits(digits, optionalParams); + } + +/** + * Hangup a call request + * @method + * @param {string} callUUID - call uuid to send digits on a call + * @promise {object} returns PlivoGenericResponse Object + * @fail {Error} returns Error + */ + cancel(id) { + let errors = validate([ + {field: 'call_uuid', value: id, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Call(this[clientKey], { + id: id + }).cancel(); + } + + listLiveCalls() { + return this[liveCallInterfaceKey].list(); + } + + getLiveCall(id) { + return this[liveCallInterfaceKey].get(id); + } +} + +export class LiveCallResource extends PlivoResource { + constructor(client, data = {}) { + super(action, LiveCallResource, idField, client); + + if (idField in data) { + this.id = data[idField]; + } + + extend(this, data); + this[clientKey] = client; + } +} + +/** + * Represents a LiveCall interface + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +class LiveCallInterface extends PlivoResourceInterface { + constructor(client, data = {}) { + super(action, LiveCallResource, idField, client); + extend(this, data); + + this[clientKey] = client; + } + + /** + * Get A Live Call Detail + * @method + * @param {string} id - call uuid to get information of. + * @promise {object} returns LiveCallResource Object + * @fail {Error} returns Error + */ + get(id) { + let errors = validate([{field: 'id', value: id, validators: ['isRequired']}]); + + if (errors) { + return errors; + } + return super.get(id, { + status: 'live', + }); + } + + list() { + let client = this[clientKey]; + + return new Promise((resolve, reject) => { + client('GET', action, {status: 'live'}) + .then(response => { + let calls = []; + response.body.calls.forEach(callUuid => { + calls.push(new LiveCallResource(client, { + callUuid: callUuid + })); + }); + resolve(calls); + }) + .catch(error => { + reject(error); + }); + }); + } +} diff --git a/lib/resources/conferences.js b/lib/resources/conferences.js new file mode 100644 index 00000000..9730943a --- /dev/null +++ b/lib/resources/conferences.js @@ -0,0 +1,662 @@ +import {extend, validate} from '../utils/common.js'; +import {PlivoResource, PlivoResourceInterface} from '../base'; + +const clientKey = Symbol(); +const action = 'Conference/'; +const idField = 'conferenceName'; + +/** + * Represents a Conference + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class Conference extends PlivoResource { + constructor(client, data = {}) { + super(action, Conference, idField, client); + + if (idField in data) { + this.id = data[idField]; + } + + extend(this, data); + this[clientKey] = client; + } + +/** + * hangup conference + * @method + * @promise {Boolean} return true if call hung up + * @fail {Error} return Error + */ + hangup() { + return super.delete(); + } + +/** + * hangup member from conference + * @method + * @param {string} memberId - id of member to be hangup + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + hangupMember(memberId) { + let errors = validate([ + {field: 'member_id', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return super.executeAction(this.id + '/Member/' + memberId + '/', 'DELETE'); + } + +/** + * kick member from conference + * @method + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + kickMember(memberId) { + let errors = validate([ + {field: 'member_id', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + + return super.executeAction(this.id + '/Member/' + memberId + '/Kick/', 'POST'); + } + +/** + * mute member from conference + * @method + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + muteMember(memberId) { + let errors = validate([ + {field: 'member_id', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return super.executeAction(this.id + '/Member/' + memberId + '/Mute/', 'POST'); + } + +/** + * unmute member from conference + * @method + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + unmuteMember(memberId) { + let errors = validate([ + {field: 'member_id', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + + return super.executeAction(this.id + '/Member/' + memberId + '/Mute/', 'DELETE'); + } + +/** + * deaf member from conference + * @method + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + deafMember(memberId) { + let errors = validate([ + {field: 'member_id', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return super.executeAction(this.id + '/Member/' + memberId + '/Deaf/', 'POST'); + } + +/** + * undeaf member from conference + * @method + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + undeafMember(memberId) { + let errors = validate([ + {field: 'member_id', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return super.executeAction(this.id + '/Member/' + memberId + '/Deaf/', 'DELETE'); + } + +/** + * play audio to member + * @method + * @param {string} memberId - id of member + * @param {string} url - url for audio + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + playAudioToMember(memberId, url) { + let errors = validate([ + {field: 'member_id', value: memberId, validators: ['isRequired']}, + {field: 'url', value: url, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + let params = {url: url}; + return super.executeAction(this.id + '/Member/' + memberId + '/Play/', 'POST', params); + } + +/** + * stop playing audio to member + * @method + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + stopPlayingAudioToMember(memberId) { + let errors = validate([ + {field: 'member_id', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return super.executeAction(this.id + '/Member/' + memberId + '/Play/', 'DELETE'); + } + +/** + * speak text to member + * @method + * @param {string} memberId - id of member + * @param {string} text - text to be speak to member + * @param {object} optionalParams - optionalPrams to speak text + * @param {string} [optionalParams.voice] The voice to be used. Can be MAN or WOMAN. Defaults to WOMAN. + * @param {string} [optionalParams.language] The language to be used. Defaults to en-US. + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + speakTextToMember(memberId, text, optionalParams) { + let errors = validate([ + {field: 'member_id', value: memberId, validators: ['isRequired']}, + {field: 'text', value: text, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + let params = optionalParams || {}; + params.text = text; + + return super.executeAction(this.id + '/Member/' + memberId + '/Speak/', 'POST', params); + } + +/** + * stop speaking text to member + * @method + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + stopSpeakingTextToMember(memberId) { + let errors = validate([ + {field: 'member_id', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return super.executeAction(this.id + '/Member/' + memberId + '/Speak/', 'DELETE'); + } + + /** + * Record conference + * @method + * @param {object} params - optional params to record conference + * @param {string} [params.fileFormat] The file format of the record can be of mp3 or wav format. Defaults to mp3 format. + * @param {string} [params.transcriptionType] The type of transcription required. The following values are allowed: + * - auto - This is the default value. Transcription is completely automated; turnaround time is about 5 minutes. + * - hybrid - Transcription is a combination of automated and human verification processes; turnaround time is about 10-15 minutes. + * @param {string} [params.transcriptionUrl] The URL where the transcription is available. + * @param {string} [params.transcriptionMethod] The method used to invoke the transcription_url. Defaults to POST. + * @param {string} [params.callbackUrl] The URL invoked by the API when the recording ends. + * @param {string} [params.callbackMethod] The method which is used to invoke the callback_url URL. Defaults to POST. + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + record(params) { + return this.startRecording(params); + } + + /** + * Record conference + * @method + * @param {object} params - optional params to record conference + * @param {string} [params.fileFormat] The file format of the record can be of mp3 or wav format. Defaults to mp3 format. + * @param {string} [params.transcriptionType] The type of transcription required. The following values are allowed: + * - auto - This is the default value. Transcription is completely automated; turnaround time is about 5 minutes. + * - hybrid - Transcription is a combination of automated and human verification processes; turnaround time is about 10-15 minutes. + * @param {string} [params.transcriptionUrl] The URL where the transcription is available. + * @param {string} [params.transcriptionMethod] The method used to invoke the transcription_url. Defaults to POST. + * @param {string} [params.callbackUrl] The URL invoked by the API when the recording ends. + * @param {string} [params.callbackMethod] The method which is used to invoke the callback_url URL. Defaults to POST. + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + startRecording(params) { + return super.executeAction(this.id + '/Record/', 'POST', params); + } + +/** + * stop recording conference + * @method + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + stopRecording() { + return super.executeAction(this.id + '/Record/', 'DELETE'); + } +} + +/** + * Represents a Conference Interface + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class ConferenceInterface extends PlivoResourceInterface { + + constructor(client, data = {}) { + super(action, Conference, idField, client); + extend(this, data); + + this[clientKey] = client; + } + +/** + * get conference by id + * @method + * @param {string} id - id of conference + * @promise {@link Conference} return {@link Conference} object if success + * @fail {Error} return Error + */ + get(id) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return super.get(id); + } + +/** + * get all conferences. returns name of all conferences + * @method + * @promise {@link [Conference]} returns list of {@link Conference} objects if success + * @fail {Error} return Error + */ + list() { + let client = this[clientKey]; + + return new Promise((resolve, reject) => { + client('GET', action) + .then(response => { + let conferences = []; + response.body.conferences.forEach(conference => { + conferences.push(new Conference(client, { + name: conference + })); + }); + resolve(conferences); + }) + .catch(error => { + reject(error); + }); + }); + } + +/** + * hangup conference + * @method + * @param {string} conferenceName - name of conference + * @promise {@link Conference} return {@link Conference} object if success + * @fail {Error} return Error + */ + hangup(conferenceName) { + let errors = validate([ + {field: 'conference_name', value: conferenceName, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Conference(this[clientKey], { + id: conferenceName + }).delete(); + } + +/** + * hangup all + * @method + * @promise {@link PlivoGenericResponse} returns object of PlivoGenericResponse if success + * @fail {Error} return Error + */ + hangupAll() { + return new Conference(this[clientKey]) + .executeAction('', 'DELETE'); + } + +/** + * hangup member from conference + * @method + * @param {string} id - id of conference + * @param {string} memberId - id of member to be hangup + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + hangupMember(id, memberId) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']}, + {field: 'memberId', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Conference(this[clientKey], { + id: id + }).hangupMember(memberId); + } + +/** + * kick member from conference + * @method + * @param {string} id - id of conference + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + kickMember(id, memberId) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']}, + {field: 'memberId', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Conference(this[clientKey], { + id: id + }).kickMember(memberId); + } + +/** + * mute member + * @method + * @param {string} id - id of conference + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + muteMember(id, memberId) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']}, + {field: 'memberId', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Conference(this[clientKey], { + id: id + }).muteMember(memberId); + } + +/** + * unmute member + * @method + * @param {string} id - id of conference + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + unmuteMember(id, memberId) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']}, + {field: 'memberId', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Conference(this[clientKey], { + id: id + }).unmuteMember(memberId); + } + +/** + * deaf member + * @method + * @param {string} id - id of conference + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + deafMember(id, memberId) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']}, + {field: 'memberId', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Conference(this[clientKey], { + id: id + }).deafMember(memberId); + } + +/** + * undeaf member + * @method + * @param {string} id - id of conference + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + undeafMember(id, memberId) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']}, + {field: 'memberId', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Conference(this[clientKey], { + id: id + }).undeafMember(memberId); + } +/** + * play audio to member + * @method + * @param {string} id - id of conference + * @param {string} memberId - id of member + * @param {string} url - urls for audio + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + + playAudioToMember(id, memberId, url) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']}, + {field: 'memberId', value: memberId, validators: ['isRequired']}, + {field: 'url', value: url, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Conference(this[clientKey], { + id: id + }).playAudioToMember(memberId, url); + } + +/** + * stop playing audio to member + * @method + * @param {string} id - id of conference + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + stopPlayingAudioToMember(id, memberId) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']}, + {field: 'memberId', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Conference(this[clientKey], { + id: id + }).stopPlayingAudioToMember(memberId); + } + +/** + * speak text to member + * @method + * @param {string} id - id of conference + * @param {string} memberId - id of member + * @param {string} text - text to speak + * @param {object} optionalParams - optional params + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + speakTextToMember(id, memberId, text, optionalParams) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']}, + {field: 'memberId', value: memberId, validators: ['isRequired']}, + {field: 'text', value: text, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Conference(this[clientKey], { + id: id + }).speakTextToMember(memberId, text, optionalParams); + } + +/** + * stop speaking text to member + * @method + * @param {string} id - id of conference + * @param {string} memberId - id of member + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + stopSpeakingTextToMember(id, memberId) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']}, + {field: 'memberId', value: memberId, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Conference(this[clientKey], { + id: id + }).stopSpeakingTextToMember(memberId); + } + +/** + * record conference + * @method + * @param {string} id - id of conference + * @param {object} params - optional params to record conference + * @param {string} [params.fileFormat] The file format of the record can be of mp3 or wav format. Defaults to mp3 format. + * @param {string} [params.transcriptionType] The type of transcription required. The following values are allowed: + * - auto - This is the default value. Transcription is completely automated; turnaround time is about 5 minutes. + * - hybrid - Transcription is a combination of automated and human verification processes; turnaround time is about 10-15 minutes. + * @param {string} [params.transcriptionUrl] The URL where the transcription is available. + * @param {string} [params.transcriptionMethod] The method used to invoke the transcription_url. Defaults to POST. + * @param {string} [params.callbackUrl] The URL invoked by the API when the recording ends. + * @param {string} [params.callbackMethod] The method which is used to invoke the callback_url URL. Defaults to POST. + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + record(id, params) { + return this.startRecording(id, params); + } + +/** + * record conference + * @method + * @param {string} id - id of conference + * @param {object} params - optional params to record conference + * @param {string} [params.fileFormat] The file format of the record can be of mp3 or wav format. Defaults to mp3 format. + * @param {string} [params.transcriptionType] The type of transcription required. The following values are allowed: + * - auto - This is the default value. Transcription is completely automated; turnaround time is about 5 minutes. + * - hybrid - Transcription is a combination of automated and human verification processes; turnaround time is about 10-15 minutes. + * @param {string} [params.transcriptionUrl] The URL where the transcription is available. + * @param {string} [params.transcriptionMethod] The method used to invoke the transcription_url. Defaults to POST. + * @param {string} [params.callbackUrl] The URL invoked by the API when the recording ends. + * @param {string} [params.callbackMethod] The method which is used to invoke the callback_url URL. Defaults to POST. + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + startRecording(id, params) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + + return new Conference(this[clientKey], { + id: id + }).startRecording(params); + } + +/** + * stop recording + * @method + * @param {string} id - id of conference + * @promise {PlivoGenericResponse} return PlivoGenericResponse if success + * @fail {Error} return Error + */ + stopRecording(id) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Conference(this[clientKey], { + id: id + }).stopRecording(); + } +} diff --git a/lib/resources/endpoints.js b/lib/resources/endpoints.js new file mode 100644 index 00000000..d3103b2a --- /dev/null +++ b/lib/resources/endpoints.js @@ -0,0 +1,157 @@ +import {extend, validate} from '../utils/common.js'; +import {PlivoResource, PlivoResourceInterface} from '../base'; + +const clientKey = Symbol(); +const action = 'Endpoint/'; +const idField = 'endpointId'; + +/** + * Represents a Endpoint + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ + +export class Endpoint extends PlivoResource { + constructor(client, data = {}) { + super(action, Endpoint, idField, client); + + if (idField in data) { + this.id = data[idField]; + } + + extend(this, data); + this[clientKey] = client; + } + +/** + * update Endpoint + * @method + * @param {object} params + * @param {string} [params.username] - username to update + * @param {string} [params.password] - password to update + * @param {string} [params.alias] - alias to update + * @param {string} [params.appId] - app id to update + * @promise {object} return {@link Endpoint} object if success + * @fail {Error} return Error + */ + update(params) { + return super.update(params); + } + +/** + * delete Endpoint + * @method + * @promise {boolean} return true if success + * @fail {Error} return Error + */ + delete() { + return super.delete(); + } + +} +/** + * Represents a Endpoint Interface + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ + +export class EndpointInterface extends PlivoResourceInterface { + + constructor(client, data = {}) { + super(action, Endpoint, idField, client); + extend(this, data); + + this[clientKey] = client; + } + +/** + * Get Endpoint by given id + * @method + * @param {string} id - id of endpoint + * @promise {object} return {@link Endpoint} object if success + * @fail {Error} return Error + */ + get(id) { + return super.get(id); + } + +/** + * Create Endpoint + * @method + * @param {string} username - username to create + * @param {string} passwowrd - password to create + * @param {string} alias - alias to create + * @param {string} appId - app id to create + * @promise {object} return {@link PlivoGenericResponse} object if success + * @fail {Error} return Error + */ + create(username, password, alias, appId) { + let params = {}; + + let errors = validate([ + {field: 'username', value: username, validators: ['isRequired']}, + {field: 'password', value: password, validators: ['isRequired']}, + {field: 'alias', value: alias, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + + params.username = username; + params.password = password; + params.alias = alias; + if (appId) { + params.app_id = appId; + } + + return super.create(params); + } + +/** + * update Endpoint + * @method + * @param {string} id - id to update + * @param {object} params + * @param {string} [params.username] - username to update + * @param {string} [params.password] - password to update + * @param {string} [params.alias] - alias to update + * @param {string} [params.appId] - app id to update + * @promise {object} return {@link Endpoint} object if success + * @fail {Error} return Error + */ + update(id, params) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Endpoint(this[clientKey], { + id: id + }).update(params); + } + +/** + * delete Endpoint + * @method + * @param {string} id - id to delete + * @promise {boolean} return true if success + * @fail {Error} return Error + */ + delete(id) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Endpoint(this[clientKey], { + id: id + }).delete(); + } +} diff --git a/lib/resources/messages.js b/lib/resources/messages.js new file mode 100644 index 00000000..fef9ae67 --- /dev/null +++ b/lib/resources/messages.js @@ -0,0 +1,109 @@ + +import {extend, validate} from '../utils/common.js'; +import {PlivoResource, PlivoResourceInterface} from '../base'; +import * as _ from "lodash"; + +const action = 'Message/'; +const idField = 'messageUuid'; + +/** + * Represents a Message + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class Message extends PlivoResource { + constructor(client, data = {}) { + super(action, Message, idField, client); + + if (idField in data) { + this.id = data[idField]; + } + + extend(this, data); + } +} +/** + * Represents a Message Interface + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ + +export class MessageInterface extends PlivoResourceInterface { + + constructor(client, data = {}) { + super(action, Message, idField, client); + extend(this, data); + } + +/** + * Send Message + * @method + * @param {string} src - source number + * @param {string} dst - destination number + * @param {string} text - text to send + * @param {object} optionalParams - Optional Params to send message + * @param {string} [optionalParams.type] - The type of message. Should be `sms` for a text message. Defaults to `sms`. + * @param {string} [optionalParams.url] The URL to which with the status of the message is sent. + * @param {string} [optionalParams.method] The method used to call the url. Defaults to POST. + * @param {boolean} [optionalParams.log] If set to false, the content of this message will not be logged on the Plivo infrastructure and the dst value will be masked (e.g., 141XXXXX528). Default is set to true. + * @promise {object} return {@link PlivoGenericMessage} object if success + * @fail {Error} return Error + */ + send(src, dst, text, optionalParams) { + return this.create(src, dst, text, optionalParams); + } + +/** + * Send Message + * @method + * @param {string} src - source number + * @param {string} dst - destination number + * @param {string} text - text to send + * @param {object} optionalParams - Optional Params to send message + * @param {string} [optionalParams.type] - The type of message. Should be `sms` for a text message. Defaults to `sms`. + * @param {string} [optionalParams.url] The URL to which with the status of the message is sent. + * @param {string} [optionalParams.method] The method used to call the url. Defaults to POST. + * @param {boolean} [optionalParams.log] If set to false, the content of this message will not be logged on the Plivo infrastructure and the dst value will be masked (e.g., 141XXXXX528). Default is set to true. + * @promise {object} return {@link PlivoGenericMessage} object if success + * @fail {Error} return Error + */ + create(src, dst, text, optionalParams) { + let errors = validate([ + {field: 'src', value: src, validators: ['isRequired']}, + {field: 'dst', value: dst, validators: ['isRequired']}, + {field: 'text', value: text, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + + let params = optionalParams || {}; + params.src = src; + params.dst = _.isArray(dst) ? _.join(dst, '<') : dst; + params.text = text; + + return super.create(params); + } + +/** + * Get Message by given id + * @method + * @param {string} id - id of message + * @promise {object} return {@link Message} object if success + * @fail {Error} return Error + */ + get(id) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + + return super.get(id); + } +} diff --git a/lib/resources/numbers.js b/lib/resources/numbers.js new file mode 100644 index 00000000..f927bf7b --- /dev/null +++ b/lib/resources/numbers.js @@ -0,0 +1,241 @@ +import {extend, validate} from '../utils/common.js'; +import {PlivoResource, PlivoResourceInterface} from '../base'; + +const clientKey = Symbol(); +const action = 'Number/'; +const idField = 'number'; + +/** + * Represents a PhoneNumber + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class PhoneNumber extends PlivoResource { + constructor(client, data = {}) { + super('PhoneNumber/', PhoneNumber, idField, client); + + if (idField in data) { + this.id = data[idField]; + } + + extend(this, data); + this[clientKey] = client; + } + +/** + * Buy Phone Number + * @method + * @param {string} appId - app id + * @promise {@link PlivoGenericResponse} return PlivoGenericResponse Object if success + * @fail {Error} return Error + */ + buy(appId) { + return new PhoneNumberInterface(this[clientKey], { + id: this.id + }).buy(appId); + } +} + +/** + * Represents a PhoneNumbers Interface + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + * @param {string} [data.test] - test data + */ +export class PhoneNumberInterface extends PlivoResourceInterface { + constructor(client, data = {}) { + super('PhoneNumber/', PhoneNumber, idField, client); + + extend(this, data); + this[clientKey] = client; + } + +/** + * Buy Phone Number + * @method + * @param {string} appId - app id + * @promise {@link PlivoGenericResponse} return PlivoGenericResponse Object if success + * @fail {Error} return Error + */ + buy(appId) { + let params = {}; + if (appId) { + params.app_id = appId; + } + return super.create(params); + } +} + +/** + * Represents a Number + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class NumberResource extends PlivoResource { + constructor(client, data = {}) { + super(action, NumberResource, idField, client); + + if (idField in data) { + this.id = data[idField]; + } + extend(this, data); + } + +/** + * Unrent Number + * @method + * @promise {boolean} return true if success + * @fail {Error} return Error + */ + unrent() { + return super.delete(); + } + +/** + * Update Number + * @method + * @param {object} params + * @param {string} [params.appId] - app id + * @param {string} [params.subAccount] - auth_id of subaccount + * @param {string} [params.alias] - textual name of number + * @promise {@link NumberResource} return NumberResource Object if success + * @fail {Error} return Error + */ + update(params) { + return super.update(params); + } +} + +/** + * Represents a Numbers + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class NumberInterface extends PlivoResourceInterface { + + constructor(client) { + super(action, NumberResource, idField, client); + this[clientKey] = client; + } + +/** + * Buy Phone Number + * @method + * @param {string} number - number to buy + * @param {string} appId - app id + * @promise {@link PlivoGenericResponse} return PlivoGenericResponse Object if success + * @fail {Error} return Error + */ + buy(number, appId) { + let errors = validate([ + {field: 'number', value: number, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new PhoneNumber(this[clientKey], { + id: number + }).buy(appId); + } + +/** + * Add own number from carrier + * @method + * @param {string} numbers - A comma separated list of numbers that need to be added for the carrier. + * @param {string} carrier - The carrier_id of the IncomingCarrier that the number is associated with. + * @param {string} region - region that is associated with the Number + * @param {string} optionaParams - optional params + * @promise {@link PlivoGenericResponse} return PlivoGenericResponse Object if success + * @fail {Error} return Error + */ + addOwnNumber(numbers, carrier, region, optionalParams) { + let errors = validate([ + {field: 'numbers', value: numbers, validators: ['isRequired']}, + {field: 'carrier', value: carrier, validators: ['isRequired']}, + {field: 'region', value: region, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + let params = optionalParams || {}; + + params.numbers = numbers; + params.carrier = carrier; + params.region = region; + + return super.create(params); + } + +/** + * Add own number from carrier + * @method + * @param {string} countryISO - The ISO code A2 of the country + * @param {string} optionaParams - optional params + * @promise {@link PhoneNumberInterface} return PhoneNumbers Object if success + * @fail {Error} return Error + */ + search(countryISO, optionalParams) { + let errors = validate([ + {field: 'country_iso', value: countryISO, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + + let params = optionalParams || {}; + params.country_iso = countryISO; + return new PhoneNumberInterface(this[clientKey]) + .list(params); + } + +/** + * Update Number + * @method + * @param {string} number - number to update + * @param {object} params + * @param {string} [params.appId] - app id + * @param {string} [params.subAccount] - auth_id of subaccount + * @param {string} [params.alias] - textual name of number + * @promise {@link NumberResource} return NumberResource Object if success + * @fail {Error} return Error + */ + update(number, params) { + let errors = validate([ + {field: 'number', value: number, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new NumberResource(this[clientKey], { + id: number + }).update(params); + } + +/** + * Unrent Number + * @method + * @param {string} number - number to unrent + * @promise {boolean} return true if success + * @fail {Error} return Error + */ + unrent(number) { + let errors = validate([ + {field: 'number', value: number, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new NumberResource(this[clientKey], { + id: number + }).unrent(); + } +} diff --git a/lib/resources/pricings.js b/lib/resources/pricings.js new file mode 100644 index 00000000..8bf13bce --- /dev/null +++ b/lib/resources/pricings.js @@ -0,0 +1,66 @@ +import {extend, validate} from '../utils/common.js'; +import {PlivoResource, PlivoResourceInterface} from '../base'; + +const clientKey = Symbol(); +const action = 'Pricing/'; +const idField = 'countryIso'; + +/** + * Represents a Pricing + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class Pricing extends PlivoResource { + constructor(client, data = {}) { + super(action, Pricing, idField, client); + extend(this, data); + } + +/** + * Get pricings by country + * @method + * @promise {object} return {@link PlivoGenericResponse} object + * @fail {Error} return Error + */ + get() { + let params = { + country_iso: this.id + }; + return super.executeAction('', 'GET', params); + } +} +/** + * Represents a Pricing Interface + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class PricingInterface extends PlivoResourceInterface { + constructor(client, data = {}) { + super(action, Pricing, idField, client); + extend(this, data); + + this[clientKey] = client; + } + +/** + * Get pricings by country + * @method + * @param {string} countryISO - country iso to get pricings + * @promise {object} return {@link PlivoGenericResponse} object + * @fail {Error} return Error + */ + get(countryISO) { + let errors = validate([ + {field: 'country_iso', value: countryISO, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Pricing(this[clientKey], { + id: countryISO + }).get(); + } +} diff --git a/lib/resources/recordings.js b/lib/resources/recordings.js new file mode 100644 index 00000000..8f615b31 --- /dev/null +++ b/lib/resources/recordings.js @@ -0,0 +1,88 @@ +import {extend, validate} from '../utils/common.js'; +import {PlivoResource, PlivoResourceInterface} from '../base'; + +const clientKey = Symbol(); +const action = 'Recording/'; +const idField = 'recordingId'; + +/** + * Represents a Recording + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class Recording extends PlivoResource { + constructor(client, data = {}) { + super(action, Recording, idField, client); + + if (idField in data) { + this.id = data[idField]; + } + extend(this, data); + } + +/** + * Delete recording + * @method + * @promise {boolean} return true if success + * @fail {Error} return Error + */ + delete() { + return super.delete(); + } + +} + +/** + * Represents a Recording Interface + * @constructor + * @param {function} client - make api call + * @param {object} [data] - data of call + */ +export class RecordingInterface extends PlivoResourceInterface { + + constructor(client, data = {}) { + super(action, Recording, idField, client); + extend(this, data); + + this[clientKey] = client; + } + +/** + * Get recording by id + * @method + * @param {string} id - id to get recording information + * @promise {object} return {@link Pricing} object + * @fail {Error} return Error + */ + get(id) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return super.get(id); + } + +/** + * Delete recording by id + * @method + * @param {string} id - id to delete recording + * @promise {boolean} return true if success + * @fail {Error} return Error + */ + delete(id) { + let errors = validate([ + {field: 'id', value: id, validators: ['isRequired']} + ]); + + if (errors) { + return errors; + } + return new Recording(this[clientKey], { + id: id + }).delete(); + } +} diff --git a/lib/rest/client-test.js b/lib/rest/client-test.js new file mode 100644 index 00000000..b96a2ac0 --- /dev/null +++ b/lib/rest/client-test.js @@ -0,0 +1,52 @@ +import {Request} from './request-test.js'; +import {CallInterface} from '../resources/call.js'; +import {AccountInterface, SubaccountInterface} from '../resources/accounts.js'; +import {ApplicationInterface} from '../resources/applications.js'; +import {ConferenceInterface} from '../resources/conferences.js'; +import {EndpointInterface} from '../resources/endpoints.js'; +import {MessageInterface} from '../resources/messages.js'; +import {NumberInterface} from '../resources/numbers.js'; +import {PricingInterface} from '../resources/pricings.js'; +import {RecordingInterface} from '../resources/recordings.js'; +import {camelCaseRequestWrapper} from './utils'; + +export class Client { + constructor(authId, authToken, proxy) { + if (!(this instanceof Client)) { + return new Client(authId, authToken, proxy); + } + authId = authId || process.env.PLIVO_AUTH_ID; + authToken = authToken || process.env.PLIVO_AUTH_TOKEN; + + if (typeof authId === 'undefined') { + throw 'Please provide authId'; + } + if (typeof authToken === 'undefined') { + throw 'Please provide authToken'; + } + + let options = { + authId: authId, + authToken: authToken, + version: 'v1', + url: 'https://api.plivo.com/v1/Account/' + authId, + userAgent: 'NodePlivo' + }; + if (typeof proxy !== 'undefined') { + options.proxy = proxy; + } + + let client = camelCaseRequestWrapper(Request(options)); + + this.calls = new CallInterface(client); + this.accounts = new AccountInterface(client); + this.subAccounts = new SubaccountInterface(client); + this.applications = new ApplicationInterface(client); + this.conferences = new ConferenceInterface(client); + this.endpoints = new EndpointInterface(client); + this.messages = new MessageInterface(client); + this.numbers = new NumberInterface(client); + this.pricings = new PricingInterface(client); + this.recordings = new RecordingInterface(client); + } +} diff --git a/lib/rest/client.js b/lib/rest/client.js new file mode 100644 index 00000000..6d6ac125 --- /dev/null +++ b/lib/rest/client.js @@ -0,0 +1,65 @@ +import {Request} from './request.js'; +import {camelCaseRequestWrapper} from './utils'; +import {name, version} from '../../package.json'; +import {CallInterface} from "../resources/call"; +import {SubaccountInterface, AccountInterface} from "../resources/accounts"; +import {ApplicationInterface} from "../resources/applications"; +import {ConferenceInterface} from "../resources/conferences"; +import {EndpointInterface} from "../resources/endpoints"; +import {MessageInterface} from "../resources/messages"; +import {NumberInterface} from "../resources/numbers"; +import {PricingInterface} from "../resources/pricings"; +import {RecordingInterface} from "../resources/recordings"; +import {Response} from "../utils/plivoxml"; +import {validateSignature} from "../utils/security"; + +exports.Response = function () { + return new Response(); +} + +exports.validateSignature = function (uri, nonce, signature, auth_token) { + return validateSignature(uri, nonce, signature, auth_token); +} + +/** + * Plivo API client which can be used to access the Plivo APIs. + * To set a proxy or timeout, pass in options.proxy (url) or options.timeout (number in ms) + * You can also pass in additional parameters accepted by the node requests module. + */ +export class Client { + constructor(authId, authToken, options) { + if (!(this instanceof Client)) { + return new Client(authId, authToken, options); + } + authId = authId || process.env.PLIVO_AUTH_ID; + authToken = authToken || process.env.PLIVO_AUTH_TOKEN; + + if (authId == null) { + throw (new Error('Please provide authId')); + } + if (authToken == null) { + throw (new Error('Please provide authToken')); + } + + options = Object.assign({}, { + authId: authId, + authToken: authToken, + version: 'v1', + url: 'https://api.plivo.com/v1/Account/' + authId, + userAgent: `${name || 'plivo-node'}/${version || 'Unknown Version'} (Node: ${process.version})`, + }, options); + + let client = camelCaseRequestWrapper(Request(options)); + + this.calls = new CallInterface(client); + this.accounts = new AccountInterface(client); + this.subaccounts = this.subAccounts = new SubaccountInterface(client); + this.applications = new ApplicationInterface(client); + this.conferences = new ConferenceInterface(client); + this.endpoints = new EndpointInterface(client); + this.messages = new MessageInterface(client); + this.numbers = new NumberInterface(client); + this.pricings = new PricingInterface(client); + this.recordings = new RecordingInterface(client); + } +} diff --git a/lib/rest/request-test.js b/lib/rest/request-test.js new file mode 100644 index 00000000..80f8bd3b --- /dev/null +++ b/lib/rest/request-test.js @@ -0,0 +1,1074 @@ +import request from 'request'; +import queryString from 'querystring'; + +export function Request(config) { + let auth = 'Basic ' + new Buffer(config.authId + ':' + config.authToken) + .toString('base64'); + + let headers = { + Authorization: auth, + 'User-Agent': config.userAgent, + 'Content-Type': 'application/json' + }; + + return (method, action, params) => { + params = params || {}; + var options = { + url: config.url + '/' + action, + method: method, + formData: params || '', + headers: headers, + json: true + }; + + if (method == 'GET' && options.formData !== '') { + let query = '?' + queryString.stringify(params); + options.url += query; + } + + if (typeof config.proxy !== 'undefined') { + options.proxy = config.proxy; + } + + return new Promise((resolve, reject) => { + // LiveCall - needs to be at top + if (method === 'GET' && action === 'Call/6653422-91b6-4716-9fad-9463daaeeec2/' && params.status === 'live') { + resolve({ + response: {}, + body: { + "direction": "inbound", + "from": "15856338537", + "call_status": "in-progress", + "api_id": "45223222-74f8-11e1-8ea7-12313806be9a", + "to": "14154290945", + "caller_name": "+15856338537", + "call_uuid": "6653422-91b6-4716-9fad-9463daaeeec2", + "session_start": "2014-03-23 14:49:39.722551" + } + }); + } + + if (method === 'GET' && action === 'Call/' && params.status === 'live') { + resolve({ + response: {}, + body: { + "api_id": "c9527676-5839-11e1-86da-6ff39efcb949", + "calls": [ + "eac94337-b1cd-499b-82d1-b39bca50dc31", + "0a70a7fb-168e-4944-a846-4f3f4d2f96f1" + ] + } + }); + } + + if (method === 'DELETE' && action === 'Request/1/') { + resolve({ + response: {}, + body: {} + }); + } + + + // get all calls + if (action == 'Call/' && method == 'GET') { + resolve({ + response: {}, + body: { + objects: [] + } + }); + } + if (action == 'Call/' && method == 'POST') { + resolve({ + response: {}, + body: { + message: 'call fired', + request_uuid: '9834029e-58b6-11e1-b8b7-a5bd0e4e126f', + api_id: '97ceeb52-58b6-11e1-86da-77300b68f8bb' + } + }); + } + if (action == 'Call/1/' && method == 'GET') { + resolve({ + response: {}, + body: { + id: 1, + call_uuid: 'aaa-deeiei3-dfddd' + } + }); + } + if (action == 'Call/1/' && method == 'POST') { + resolve({ + response: {}, + body: { + id: 5, + call_uuid: 'aaa-deeiei3-dfddd' + } + }); + } + if (action == 'Call/1/' && method == 'DELETE') { + resolve({ + response: {}, + body: {} + }); + } + if (action == 'Call/1/Record/' && method == 'POST') { + resolve({ + response: {}, + body: { + url: 'http://s3.amazonaws.com/recordings_2013/48dfaf60-3b2a-11e3.mp3', + message: 'call recording started', + recording_id: '48dfaf60-3b2a-11e3', + api_id: 'c7b69074-58be-11e1-86da-adf28403fe48' + } + }); + } + if (action == 'Call/1/Record/' && method == 'DELETE') { + resolve({ + response: {}, + body: { + } + }); + } + if (action == 'Call/1/Play/' && method == 'POST') { + resolve({ + response: {}, + body: { + message: 'play started', + api_id: '07abfd94-58c0-11e1-86da-adf28403fe48' + } + }); + } + if (action == 'Call/1/Play/' && method == 'DELETE') { + resolve({ + response: {}, + body: {} + }); + } + if (action == 'Call/1/Speak/' && method == 'DELETE') { + resolve({ + response: {}, + body: { + message: 'speak stopped', + api_id: 'cf2359c8-f4d6-11e6-b886-067c5485c240' + } + }); + } + if (action == 'Call/1/Speak/' && method == 'POST') { + resolve({ + response: {}, + body: { + message: 'speak started', + api_id: '07abfd94-58c0-11e1-86da-adf28403fe48' + } + }); + } + if (action == 'Call/1/DTMF/' && method == 'POST') { + resolve({ + response: {}, + body: { + message: 'digits sent', + api_id: '07abfd94-58c0-11e1-86da-adf28403fe48' + } + }); + } + // if(action == 'Call/1/' && method == 'DELETE') { + // resolve({ + // response: {}, + // body: {} + // }) + // } + // + // + // Accounts + if (action == '' && method == 'GET') { + resolve({ + response: {}, + body: { + account_type: 'standard', + address: '340 Pine St, San Francisco, CA - 94104', + api_id: 'c31b36be-0da2-11e4-bd8a-12313f016a39', + auth_id: 'MANWVLYTK4ZWU1YTY4ZT', + auto_recharge: true, + billing_mode: 'prepaid', + cash_credits: '23.79822', + city: 'San Francisco', + name: 'Han Solo', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/', + state: 'California', + timezone: 'America/Los_Angeles' + } + }); + } + + if (action == '/' && method == 'POST') { + resolve({ + response: {}, + body: { + api_id: 'ea43d134-0db0-11e4-a2d1-22000ac5040c', + message: 'changed' + } + }); + } + + if(action === 'Subaccount/' && method === 'POST') { + resolve({ + response: {}, + body: { + "api_id": "97c8d1de-3f08-11e7-b6f4-061564b78b75", + "auth_id": "SANDLHYZBIZMU4ZDEXNM", + "auth_token": "MTMzZTZjNzdiNDVmY2VhZDQyNTUwYWVjNzI2OThk", + "message": "created" + } + }); + } + + if (action == 'Subaccount/1/' && method == 'GET') { + resolve({ + response: {}, + body: { + account: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/', + api_id: '323972b2-0db3-11e4-a2d1-22000ac5040c', + auth_id: '1', + auth_token: 'MTZjYWM0YzVjNjMwZmVmODFiNWJjNWJmOGJjZjgw', + created: '2014-07-17', + enabled: false, + modified: null, + name: 'Chewbacca', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Subaccount/SAMTVIYJDIYWYYMZHLYZ/' + } + }); + } + + if (action == 'Subaccount/1/' && method == 'POST') { + resolve({ + response: {}, + body: { + message: 'changed', + api_id: '5a9fcb68-523d-11e1-86da-6ff39efcb949' + } + }); + } + + if (action == 'Subaccount/' && method == 'GET') { + resolve({ + response: {}, + body: { + api_id: 'b38bf42e-0db4-11e4-8a4a-123140008edf', + meta: { + limit: 20, + next: null, + offset: 0, + previous: null, + total_count: 2 + }, + objects: [ + { + account: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/', + auth_id: '1', + auth_token: 'MTZjYWM0YzVjNjMwZmVmODFiNWJjNWJmOGJjZjgw', + created: '2014-07-17', + enabled: false, + modified: null, + name: 'Chewbacca', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Subaccount/SAMTVIYJDIYWYYMZHLYZ/' + }, + { + account: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/', + auth_id: '1', + auth_token: 'OTdhMjYwMWYxOGMyNpFjNzUwYWM3YWI3NjY4Y2Ey', + created: '2012-09-23', + enabled: true, + modified: '2012-09-23', + name: 'new', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Subaccount/SANJQ5NWEYNWZJNZE2MZ/' + } + ] + } + }); + } + + if (action == 'Subaccount/1/' && method == 'DELETE') { + resolve({ + response: {}, + body: { } + }); + } + + // ============= Application =================== + + if (action == 'Application/1/' && method == 'GET') { + resolve({ + response: {}, + body: { + answer_method: 'GET', + answer_url: 'http://webapp.com/dial.xml', + app_id: '1', + app_name: 'Dial Office', + default_app: false, + enabled: true, + fallback_answer_url: '', + fallback_method: 'POST', + hangup_method: 'POST', + hangup_url: 'http://webapp.com/dial.xml', + message_method: 'POST', + message_url: '', + public_uri: false, + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Application/20372631212782780/', + sip_uri: 'sip:20372631212782780@app.plivo.com', + sub_account: null + } + }); + } + if (action == 'Application/' && method == 'GET') { + resolve({ + response: {}, + body: { + api_id: 'e5b05b26-10c4-11e4-a2d1-22000ac5040c', + meta: { + limit: 20, + next: null, + offset: 0, + previous: null, + total_count: 19 + }, + objects: [ + { + answer_url: 'http://webapp.com/dial.xml', + answer_method: 'GET', + app_id: '20372631212782780', + app_name: 'Dial Office', + default_app: false, + enabled: true, + fallback_answer_url: '', + fallback_method: 'POST', + hangup_method: 'POST', + hangup_url: 'http://webapp.com/dial.xml', + message_method: 'POST', + message_url: '', + public_uri: false, + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Application/20372631212782780/', + sip_uri: 'sip:20372631212782780@app.plivo.com', + sub_account: null + }, + { + answer_url: 'https://webapp.com/conference_court.xml', + answer_method: 'GET', + app_id: '14260623927192078', + app_name: 'Conference_Court', + default_app: false, + enabled: true, + fallback_answer_url: '', + fallback_method: 'POST', + hangup_method: 'POST', + hangup_url: 'https://webapp.com/conference_court.xml', + message_method: 'POST', + message_url: '', + public_uri: false, + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Application/14260623927192078/', + sip_uri: 'sip:142606239271920703@app.plivo.com', + sub_account: null + } + ] + } + }); + } + if (action == 'Application/1/' && method == 'POST') { + resolve({ + response: {}, + body: { + message: 'changed', + api_id: '5a9fcb68-582d-11e1-86da-6ff39efcb949' + } + }); + } + if (action == 'Application/' && method == 'POST') { + resolve({ + response: {}, + body: { + message: 'created', + app_id: '15784735442685051', + api_id: '5a9fcb68-582d-11e1-86da-6ff39efcb949' + } + }); + } + + if (action == 'Application/1/' && method == 'DELETE') { + resolve({ + response: {}, + body: { + } + }); + } + // ============= conferences =================== + + if (action == 'Conference/MyConf/' && method == 'GET') { + resolve({ + response: {}, + body: { + conference_name: 'MyConf', + conference_run_time: '590', + conference_member_count: '1', + members: [ + { + muted: false, + member_id: '17', + deaf: false, + from: '1456789903', + to: '1677889900', + caller_name: 'John', + direction: 'inbound', + call_uuid: 'acfbf0b5-12e0-4d74-85f7-fce15f8f07ec', + join_time: '590' + } + ], + api_id: '816e903e-58c4-11e1-86da-adf28403fe48' + } + }); + } + + if (action == 'Conference/' && method == 'GET') { + resolve({ + response: {}, + body: { + api_id: '2867b6e2-58c3-11e1-86da-adf28403fe48', + conferences: [ + 'My Conf Room' + ] + } + }); + } + + if (action == 'Conference/' && method == 'DELETE') { + resolve({ + response: {}, + body: { + message: 'all conferences hung up', + api_id: '2867b6e2-58c3-11e1-86da-adf28403fe48' + } + }); + } + + if (action == 'Conference/MyConf/' && method == 'DELETE') { + resolve({ + response: {}, + body: { + message: 'conference hung up', + api_id: '2867b6e2-58c3-11e1-86da-adf28403fe48' + } + }); + } + + if (action == 'Conference/MyConf/Member/1/' && method == 'DELETE') { + resolve({ + response: {}, + body: { + message: 'hangup', + member_id: '10', + api_id: '2867b6e2-58c3-11e1-86da-adf28403fe48' + } + }); + } + + if (method == 'POST' && action == 'Conference/MyConf/Member/1/Kick/') { + resolve({ + response: {}, + body: { + message: 'kicked', + member_id: '10', + api_id: '2867b6e2-58c3-11e1-86da-adf28403fe48' + } + }); + } + + if (method == 'POST' && action == 'Conference/MyConf/Member/1/Mute/') { + resolve({ + response: {}, + body: { + message: 'muted', + member_id: '10', + api_id: '2867b6e2-58c3-11e1-86da-adf28403fe48' + } + }); + } + + if (method == 'DELETE' && action == 'Conference/MyConf/Member/1/Mute/') { + resolve({ + response: {}, + body: {} + }); + } + + if (method == 'POST' && action == 'Conference/MyConf/Member/1/Deaf/') { + resolve({ + response: {}, + body: { + message: 'deaf', + member_id: '10', + api_id: '2867b6e2-58c3-11e1-86da-adf28403fe48' + } + }); + } + + if (method == 'DELETE' && action == 'Conference/MyConf/Member/1/Deaf/') { + resolve({ + response: {}, + body: { } + }); + } + + if (method == 'POST' && action == 'Conference/MyConf/Member/1/Play/') { + resolve({ + response: {}, + body: { + message: 'play queued into conference', + api_id: '4e44bd4e-f830-11e6-b886-067c5485c240', + member_id: '[u\'160005\', u\'160004\', u\'160003\', u\'160002\']' + } + }); + } + + if (method == 'DELETE' && action == 'Conference/MyConf/Member/1/Play/') { + resolve({ + response: {}, + body: { + message: 'playing in conference stopped', + member_id: '10', + api_id: '2867b6e2-58c3-11e1-86da-adf28403fe48' + } + }); + } + + if (method == 'POST' && action == 'Conference/MyConf/Member/1/Speak/') { + resolve({ + response: {}, + body: { + message: 'speak queued into conference', + api_id: '8dd6820e-fe83-11e6-b6f4-061564b78b75', + member_id: '[u\'all\']' + } + }); + } + + if (method == 'DELETE' && action == 'Conference/MyConf/Member/1/Speak/') { + resolve({ + response: {}, + body: { + message: 'speak stopped', + member_id: '10', + api_id: '2867b6e2-58c3-11e1-86da-adf28403fe48' + } + }); + } + + if (method == 'DELETE' && action == 'Conference/MyConf/Record/') { + resolve({ + response: {}, + body: { } + }); + } + + if (method == 'POST' && action == 'Conference/MyConf/Record/') { + resolve({ + response: {}, + body: { + api_id: '2867b6e2-58c3-11e1-86da-adf28403fe48', + message: 'conference recording started', + recording_id: '93bc7c6a-3b2b-11e3', + url: 'http://s3.amazonaws.com/recordings_2013/93bc7c6a-3b2b-11e3.mp3' + } + }); + } + + // ============= Numbers =================== + if (method == 'GET' && action == 'Number/+919999999990/') { + resolve({ + response: {}, + body: { + added_on: '2014-02-14', + alias: null, + api_id: '88625e5e-1c92-11e4-80aa-12313f048015', + application: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Application/29986316244302815/', + carrier: 'Plivo', + monthly_rental_rate: '0.80000', + number: '+919999999990', + number_type: 'local', + region: 'California, UNITED STATES', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Number/17609915566/', + sms_enabled: true, + sms_rate: '0.00000', + sub_account: null, + voice_enabled: true, + voice_rate: '0.00850' + } + }); + } + + if (method == 'GET' && action == 'Number/') { + resolve({ + response: {}, + body: { + api_id: '114de006-1c95-11e4-8a4a-123140008edf', + meta: { + limit: 3, + next: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Number/?limit=3&offset=3', + offset: 0, + previous: null, + total_count: 20 + }, + objects: [{ + added_on: '2014-08-05', + alias: null, + application: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Application/29986316244302815/', + carrier: 'Plivo', + monthly_rental_rate: '0.80000', + number: '18135401302', + number_type: 'local', + region: 'Florida, UNITED STATES', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Number/18135401302/', + sms_enabled: true, + sms_rate: '0.00000', + sub_account: null, + voice_enabled: true, + voice_rate: '0.00850' + }] + } + }); + } + + if (method == 'POST' && action == 'Number/') { + resolve({ + response: {}, + body: { + message: 'changed', + api_id: '5a9fcb68-582d-11e1-86da-6ff39efcb949' + } + }); + } + + if (method == 'POST' && action == 'Number/+919999999990/') { + resolve({ + response: {}, + body: { + message: 'changed', + api_id: '5a9fcb68-582d-11e1-86da-6ff39efcb949' + } + }); + } + + if (method == 'DELETE' && action == 'Number/+919999999990/') { + resolve({ + response: {}, + body: {} + }); + } + + if (method == 'GET' && action == 'PhoneNumber/') { + resolve({ + response: {}, + body: { + api_id: '859428b0-1c88-11e4-a2d1-22000ac5040c', + meta: { + limit: 20, + next: null, + offset: 0, + previous: null, + total_count: 9 + }, + objects: [ + { + country: 'UNITED STATES', + lata: 722, + monthly_rental_rate: '0.80000', + number: '14154009186', + type: 'fixed', + prefix: '415', + rate_center: 'SNFC CNTRL', + region: 'United States', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/PhoneNumber/14154009186/', + restriction: null, + restriction_text: null, + setup_rate: '0.00000', + sms_enabled: true, + sms_rate: '0.00800', + voice_enabled: true, + voice_rate: '0.00500' + }, + { + country: 'UNITED STATES', + lata: 722, + monthly_rental_rate: '0.80000', + number: '14154009187', + type: 'fixed', + prefix: '415', + rate_center: 'SNFC CNTRL', + region: 'United States', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/PhoneNumber/14154009187/', + restriction: null, + restriction_text: null, + setup_rate: '0.00000', + sms_enabled: true, + sms_rate: '0.00800', + voice_enabled: true, + voice_rate: '0.00500' + } + ] + } + }); + } + + if (method == 'POST' && action == 'PhoneNumber/+919999999990/') { + resolve({ + response: {}, + body: { + api_id: 'aa52882c-1c88-11e4-bd8a-12313f016a39', + message: 'created', + numbers: [ + { + number: '14154009186', + status: 'Success' + } + ], + status: 'fulfilled' + } + }); + } + // ============= Endpoint =================== + + if (action == 'Endpoint/1/' && method == 'GET') { + resolve({ + response: {}, + body: { + alias: 'zumba', + api_id: '39015de8-4fb3-11e4-a2d1-22000ac5040c', + application: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Application/379619814477342321/', + endpoint_id: '1', + password: '8bc0002a467b8276aaaf47e92bc46b9f', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Endpoint/39452475478853/', + sip_registered: 'false', + sip_uri: 'sip:zumba141009125224@phone.plivo.com', + sub_account: null, + username: 'zumba141009125224' + } + }); + } + if (action == 'Endpoint/' && method == 'GET') { + resolve({ + response: {}, + body: { + api_id: '30a0c8c2-110c-11e4-bd8a-12313f016a39', + meta: { + limit: 20, + next: null, + offset: 0, + previous: null, + total_count: 11 + }, + objects: [ + { + alias: 'callme', + application: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Application/33406267401237901/', + endpoint_id: '32866729519064', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Endpoint/32866729519064/', + sip_contact: 'sip:callme140703093224@122.172.71.207:57563;ob', + sip_expires: '2014-07-21 19:26:08', + sip_registered: 'true', + sip_uri: 'sip:callme140703093944@phone.plivo.com', + sip_user_agent: 'Telephone 1.1.4', + sub_account: null, + username: 'callme140703093944' + }, + { + alias: 'polycom', + application: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Application/37961981447734951/', + endpoint_id: '17551316589618', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Endpoint/17551316589618/', + sip_registered: 'false', + sip_uri: 'sip:polycom140506175228@phone.plivo.com', + sub_account: null, + username: 'polycom140506175448' + } + ] + } + }); + } + if (action == 'Endpoint/1/' && method == 'POST') { + resolve({ + response: {}, + body: { + message: 'changed', + api_id: 'd8f9ea6c-58cc-11e1-86da-adf28403fe48' + } + }); + } + if (action == 'Endpoint/' && method == 'POST') { + resolve({ + response: {}, + body: { + username: 'zumba131031145958', + alias: 'zumba', + message: 'created', + endpoint_id: '37371860103666', + api_id: '1c13de4c-423d-11e3-9899-22000abfa5d5' + } + }); + } + + if (action == 'Endpoint/1/' && method == 'DELETE') { + resolve({ + response: {}, + body: { + message: 'changed', + api_id: 'd8f9ea6c-58cc-11e1-86da-adf28403fe48' + } + }); + } + + // ============= Message =================== + + if (action == 'Message/1/' && method == 'GET') { + resolve({ + response: {}, + body: { + api_id: '035eeada-6df1-11e6-b608-06a72a185e87', + error_code: '200', + from_number: '18552828641', + message_direction: 'outbound', + message_state: 'failed', + message_time: '2016-08-17 21:22:36+05:30', + message_type: 'sms', + message_uuid: '1', + resource_uri: '/v1/Account/{auth_id}/Message/2a340179-e8a9-4b1d-ae2c-9f346e7b6d7d/', + to_number: '19352326448', + total_amount: '0.00000', + total_rate: '0.00350', + units: 1 + } + }); + } + if (action == 'Message/' && method == 'GET') { + resolve({ + response: {}, + body: { + api_id: '88415194-6df0-11e6-b608-06a72a185e87', + meta: { + limit: 20, + next: '/v1/Account/{auth_id}/Message/?limit=20&error_code=200&offset=20', + offset: 0, + previous: null, + total_count: 22 + }, + objects: [ + { + error_code: '200', + from_number: '18552828641', + message_direction: 'outbound', + message_state: 'failed', + message_time: '2016-08-17 21:26:44+05:30', + message_type: 'sms', + message_uuid: '85ce8068-6fab-4f0a-9dc7-d6c852cdde91', + resource_uri: '/v1/Account/{auth_id}/Message/85ce8068-6fab-4f0a-9dc7-d6c852cdde91/', + to_number: '19352326448', + total_amount: '0.00000', + total_rate: '0.00350', + units: 1 + }, + { + error_code: '200', + from_number: '18552828641', + message_direction: 'outbound', + message_state: 'failed', + message_time: '2016-08-17 21:22:36+05:30', + message_type: 'sms', + message_uuid: '2a340179-e8a9-4b1d-ae2c-9f346e7b6d7d', + resource_uri: '/v1/Account/{auth_id}/Message/2a340179-e8a9-4b1d-ae2c-9f346e7b6d7d/', + to_number: '19352326448', + total_amount: '0.00000', + total_rate: '0.00350', + units: 1 + } + ] + } + }); + } + if (action == 'Message/' && method == 'POST') { + resolve({ + response: {}, + body: { + message: 'message(s) queued', + message_uuid: ['db3ce55a-7f1d-11e1-8ea7-1231380bc196'], + api_id: 'db342550-7f1d-11e1-8ea7-1231380bc196' + } + }); + } + + // ============= Pricings =================== + if (method == 'GET' && action == 'Pricing/') { + resolve({ + response: {}, + body: { + api_id: '25b3d816-1c9f-11e4-bd8a-12313f016a39', + country: 'United States', + country_code: 1, + country_iso: 'US', + message: { + inbound: { + rate: '0.00000' + }, + outbound: { + rate: '0.00650' + }, + outbound_networks_list: [ + { + group_name: 'US', + rate: '0.00650' + }, + { + group_name: 'US', + rate: '0.00650' + } + ] + }, + phone_numbers: { + local: { + rate: '0.80000' + }, + tollfree: { + rate: '1.00000' + } + }, + voice: { + inbound: { + ip: { + rate: '0.00300' + }, + local: { + rate: '0.00850' + }, + tollfree: { + rate: '0.02100' + } + }, + outbound: { + ip: { + rate: '0.00300' + }, + local: { + rate: '0.01200' + }, + rates: [ + { + prefix: [ + '1' + ], + rate: '0.01200' + }, + { + prefix: [ + '1340' + ], + rate: '0.02400' + }, + { + prefix: [ + '1808' + ], + rate: '0.03400' + }, + { + prefix: [ + '1907' + ], + rate: '0.17900' + }, + { + prefix: [ + '1900' + ], + rate: '0.60300' + } + ], + tollfree: { + rate: '0.00300' + } + } + } + } + }); + } + // ============= Recordings =================== + if (method == 'GET' && action == 'Recording/1/') { + resolve({ + response: {}, + body: { + add_time: '2014-08-05 16:15:15.852059+05:30', + api_id: '7abf0744-1ca0-11e4-a2d1-22000ac5040c', + call_uuid: 'c2c128e2-1c8c-11e4-9bff-1db8a9db0432', + conference_name: 'noname', + recording_duration_ms: '345100.00000', + recording_end_ms: '1407235509007.00000', + recording_format: 'mp3', + recording_id: '1', + recording_start_ms: '1407235163907.00000', + recording_type: 'conference', + recording_url: 'http://s3.amazonaws.com/recordings_2013/c2186400-1c8c-11e4-a664-0026b945b52x.mp3', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Recording/c2186400-1c8c-11e4-a664-0026b945b52x/' + } + }); + } + + if (method == 'GET' && action == 'Recording/') { + resolve({ + response: {}, + body: { + api_id: 'ff25223a-1c9f-11e4-80aa-12313f048015', + meta: { + limit: 3, + next: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Recording/?limit=3&offset=3', + offset: 0, + previous: null, + total_count: 948 + }, + objects: [ + { + add_time: '2014-08-05 16:15:15.852059+05:30', + call_uuid: 'c2c128e2-1c8c-11e4-9bff-1db8a9db0432', + conference_name: 'noname', + recording_duration_ms: '345100.00000', + recording_end_ms: '1407235509007.00000', + recording_format: 'mp3', + recording_id: 'c2186400-1c8c-1124-a664-0026b945b522', + recording_start_ms: '1407235163907.00000', + recording_type: 'conference', + recording_url: 'http://s3.amazonaws.com/recordings_2013/c2186400-1c8c-1124-a664-0026b945b522.mp3', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Recording/c2186400-1c8c-1124-a664-0026b945b522/' + }, + { + add_time: '2014-08-05 16:05:21.993853+05:30', + call_uuid: 'fc773e88-1c8b-11e4-b25a-0fe7bcc54670', + conference_name: 'noname', + recording_duration_ms: '90700.00000', + recording_end_ms: '1407234920253.00000', + recording_format: 'mp3', + recording_id: 'fc2716b0-1c8b-11e4-bwad-842b2b17453e', + recording_start_ms: '1407234829553.00000', + recording_type: 'conference', + recording_url: 'http://s3.amazonaws.com/recordings_2013/fc2716b0-1c8b-11e4-bwad-842b2b17453e.mp3', + resource_uri: '/v1/Account/MANWVLYTK4ZWU1YTY4ZT/Recording/fc2716b0-1c8b-11e4-bwad-842b2b17453e/' + } + ] + } + }); + } + + if (method == 'DELETE' && action == 'Recording/1/') { + resolve({ + response: {}, + body: { } + }); + } + + reject(new Error('not found')); + }); + }; +} diff --git a/lib/rest/request.js b/lib/rest/request.js new file mode 100644 index 00000000..fb93aba4 --- /dev/null +++ b/lib/rest/request.js @@ -0,0 +1,63 @@ +import request from 'request'; +import queryString from 'querystring'; +import * as Exceptions from '../utils/exceptions'; +import * as _ from "lodash"; + +export function Request(config) { + let auth = 'Basic ' + new Buffer(config.authId + ':' + config.authToken) + .toString('base64'); + + let headers = { + Authorization: auth, + 'User-Agent': config.userAgent, + 'Content-Type': 'application/json' + }; + + return (method, action, params) => { + var options = { + url: config.url + '/' + action, + method: method, + formData: params || '', + headers: headers, + json: true + }; + + if (method === 'GET' && options.formData !== '') { + let query = '?' + queryString.stringify(params); + options.url += query; + } + + if (typeof config.proxy !== 'undefined') { + options.proxy = config.proxy; + } + + return new Promise((resolve, reject) => { + request(options, (error, response, body) => { + if (error) { + reject(error); + return; + } + + const exceptionClass = { + 400: Exceptions.InvalidRequestError, + 401: Exceptions.AuthenticationError, + 404: Exceptions.ResourceNotFoundError, + 405: Exceptions.InvalidRequestError, + 500: Exceptions.ServerError, + }[response.statusCode] || Error; + + if (!_.inRange(response.statusCode, 200, 300)) { + body = body || response.body; + if (typeof body === 'object') { + reject(new exceptionClass(JSON.stringify(body))); + } else { + reject(new exceptionClass(body)); + } + } else { + let body = response.body; + resolve({response: response, body: body}); + } + }); + }); + }; +} diff --git a/lib/rest/utils.js b/lib/rest/utils.js new file mode 100644 index 00000000..81f4cf3b --- /dev/null +++ b/lib/rest/utils.js @@ -0,0 +1,46 @@ +import _camelCase from 'lodash/camelCase'; +import _snakeCase from 'lodash/snakeCase'; +import _mapKeys from 'lodash/mapKeys'; +import _mapValues from 'lodash/mapValues'; +import _map from 'lodash/map'; + +function recursivelyRenameObject(object, renameFunc) { + if (!(object instanceof Object)) { + return object; + } + + return _mapValues(_mapKeys(object, renameFunc), function (value) { + if (Array.isArray(value)) return _map(value, function (value) { + return recursivelyRenameObject(value, renameFunc); + }); + if (typeof value !== 'object') return value; + return recursivelyRenameObject(value, renameFunc); + }); +} + +export function camelCaseRequestWrapper(requestFunc) { + return (method, action, params) => { + + params = recursivelyRenameObject(params, function (value, key) { + if(typeof key !== 'string') return key; + + return _snakeCase(key) + .replace('_less_than', '__lt') + .replace('_greater_than', '__gt') + .replace('_greater_or_equal', '__gte') + .replace('_less_or_equal', '__lte') + .replace('_equal', '') + .replace('_equals', ''); + }); + + return requestFunc(method, action, params).then(res => { + res.body = recursivelyRenameObject(res.body, function (value, key) { + if(typeof key !== 'string') return key; + return _camelCase(key); + }); + + return res; + }); + } +} + diff --git a/lib/utils/common.js b/lib/utils/common.js new file mode 100644 index 00000000..6fb9c05c --- /dev/null +++ b/lib/utils/common.js @@ -0,0 +1,69 @@ +export let extend = (instance, data) => { + data = data || {}; + for (let key in data) { + if (Object.prototype.hasOwnProperty.call(data, key)) { + instance[key] = data[key]; + } + } +}; + +export let validate = (() => { + let Validators = {}; + Validators.isDataType = (() => { + let regExs = { + String: /String/, + Number: /Number/, + Object: /Object/, + Boolean: /Boolean/, + Array: /Array/ + }; + return (object, type) => { + return regExs[type].test(Object.prototype.toString.call(object)); + }; + })(); + + // Validators.isEmpty = field => { + // if (Validators.isDataType(field, 'String')) { + // return !field.length; + // } + // return true; + // }; + + Validators.isRequired = field => { + return !field; + }; + + return (data = []) => { + let errorText = []; + + data.forEach(item => { + item.validators.forEach(validator => { + switch (validator) { + case 'isRequired': + if (Validators.isRequired(item.value)) { + errorText.push('Missing mandatory field: ' + item.field); + } + break; + // case 'isObject': + // if (!Validators.isDataType(item.value, 'Object')) { + // errorText.push(item.field + ' should be object.'); + // } + // break; + case 'isString': + if (!Validators.isDataType(item.value, 'String')) { + errorText.push(item.field + ' should be string.'); + } + break; + default: + } + }); + }); + + if (errorText.length) { + return new Promise(function (resolve, reject) { + reject(new Error(errorText.join(', '))); + }); + } + return false; + }; +})(); diff --git a/lib/utils/exceptions.js b/lib/utils/exceptions.js new file mode 100644 index 00000000..09003634 --- /dev/null +++ b/lib/utils/exceptions.js @@ -0,0 +1,6 @@ +export class PlivoRestError extends Error {} +export class ResourceNotFoundError extends PlivoRestError {} +export class ServerError extends PlivoRestError {} +export class InvalidRequestError extends PlivoRestError {} +export class PlivoXMLError extends PlivoRestError {} +export class AuthenticationError extends PlivoRestError {} diff --git a/lib/utils/plivoxml.js b/lib/utils/plivoxml.js new file mode 100644 index 00000000..73d08853 --- /dev/null +++ b/lib/utils/plivoxml.js @@ -0,0 +1,457 @@ +var qs = require('querystring'); +var xmlBuilder = require('xmlbuilder'); +var util = require('util'); + +export class PlivoXMLError extends Error {} + +/** + * Response element + * @constructor + */ +export function Response() { + this.element = 'Response'; + this.nestables = ['Speak', 'Play', 'GetDigits', 'Record', 'Dial', 'Message', + 'Redirect', 'Wait', 'Hangup', 'PreAnswer', 'Conference', 'DTMF']; + this.valid_attributes = []; + this.elem = xmlBuilder.begin().ele(this.element); +} + +Response.prototype = { + init: function (name, body, attributes, parent) { + this.name = name; + this.body = body; + this.elem = ''; + + if (this.element !== 'Response') { + this.elem = parent.ele(this.name); + this.elem.parent = parent; + } else { + this.elem = this.elem.ele(this.name); + } + + if (!attributes) { + var attributes = {}; + } + var keys = Object.keys(attributes); + + for (var i = 0; i < keys.length; i++) { + if (this.valid_attributes.indexOf(keys[i]) === -1) { + throw new PlivoXMLError('Not a valid attribute : "' + keys[i] + '" for "' + this.name + '" Element'); + } + this.elem.att(keys[i], attributes[keys[i]]) + } + + if (body) { + this.elem.text(body) + } + }, + + add: function (new_element, body, attributes) { + if (body == null) { + throw new PlivoXMLError('No text set for ' + new_element.element + '.'); + } + + if (this.nestables.indexOf(new_element.element) > -1) { + var parent = this.elem; + } else { + throw new PlivoXMLError(new_element.element + ' cannot be nested in ' + this.element + '.'); + } + new_element.init(new_element.element, body, attributes, parent); + return new_element; + }, + + /** + * Add a Conference element + * @method + * @param {string} body + * @param {object} attributes + * @param {boolean} [attributes.muted] + * @param {string} [attributes.enterSound] + * @param {string} [attributes.exitSound] + * @param {boolean} [attributes.startConferenceOnEnter] + * @param {boolean} [attributes.endConferenceOnExit] + * @param {boolean} [attributes.stayAlone] + * @param {string} [attributes.waitSound] + * @param {number} [attributes.maxMembers] + * @param {boolean} [attributes.record] + * @param {string} [attributes.recordFileFormat] + * @param {number} [attributes.timeLimit] + * @param {boolean} [attributes.hangupOnStar] + * @param {string} [attributes.action] + * @param {string} [attributes.method] + * @param {string} [attributes.callbackUrl] + * @param {string} [attributes.callbackMethod] + * @param {string} [attributes.digitsMatch] + * @param {boolean} [attributes.floorEvent] + * @param {boolean} [attributes.redirect] + * @param {boolean} [attributes.relayDTMF] + */ + addConference: function (body, attributes) { + return this.add(new Conference(Response), body, attributes); + }, + + /** + * Add a Number element + * @method + * @param {string} body + * @param {object} attributes + * @param {string} [attributes.sendDigits] + * @param {boolean} [attributes.sendOnPreanswer] + */ + addNumber: function (body, attributes) { + return this.add(new Number(Response), body, attributes); + }, + + /** + * Add a User element + * @method + * @param {string} body + * @param {object} attributes + * @param {string} [attributes.sendDigits] + * @param {boolean} [attributes.sendOnPreanswer] + * @param {string} [attributes.sipHeaders] + */ + addUser: function (body, attributes) { + return this.add(new User(Response), body, attributes); + }, + + /** + * Add a Dial element + * @method + * @param {object} attributes + * @param {string} [attributes.action] + * @param {string} [attributes.method] + * @param {boolean} [attributes.hangupOnStar] + * @param {number} [attributes.timeLimit] + * @param {number} [attributes.timeout] + * @param {string} [attributes.callerID] + * @param {string} [attributes.callerName] + * @param {string} [attributes.confirmSound] + * @param {string} [attributes.confirmKey] + * @param {string} [attributes.dialMusic] + * @param {string} [attributes.callbackUrl] + * @param {string} [attributes.callbackMethod] + * @param {boolean} [attributes.redirect] + * @param {string} [attributes.digitsMatch] + * @param {string} [attributes.digitsMatchBLeg] + * @param {string} [attributes.sipHeaders] + */ + addDial: function (attributes) { + return this.add(new Dial(Response), '', attributes); + }, + + /** + * Add a GetDigits element + * @method + * @param {object} attributes + * @param {string} [attributes.action] + * @param {string} [attributes.method] + * @param {number} [attributes.timeout] + * @param {number} [attributes.digitTimeout] + * @param {string} [attributes.finishOnKey] + * @param {number} [attributes.numDigits] + * @param {number} [attributes.retries] + * @param {boolean} [attributes.redirect] + * @param {boolean} [attributes.playBeep] + * @param {string} [attributes.validDigits] + * @param {string} [attributes.invalidDigitsSound] + * @param {boolean} [attributes.log] + */ + addGetDigits: function (attributes) { + return this.add(new GetDigits(Response), '', attributes); + }, + + /** + * Add a Hangup element + * @method + * @param {object} attributes + * @param {string} [attributes.reason] + * @param {number} [attributes.schedule] + */ + addHangup: function (attributes) { + return this.add(new Hangup(Response), '', attributes); + }, + + /** + * Add a Message element + * @method + * @param {string} body + * @param {object} attributes + * @param {string} [attributes.src] + * @param {string} [attributes.dst] + * @param {string} [attributes.type] + * @param {string} [attributes.callbackUrl] + * @param {string} [attributes.callbackMethod] + */ + addMessage: function (body, attributes) { + return this.add(new Message(Response), body, attributes); + }, + + /** + * Add a Play element + * @method + * @param {string} body + * @param {object} attributes + * @param {number} [attributes.loop] + */ + addPlay: function (body, attributes) { + return this.add(new Play(Response), body, attributes); + }, + + /** + * Add a PreAnswer element + * @method + */ + addPreAnswer: function () { + return this.add(new PreAnswer(Response), '', {}); + }, + + /** + * Add a Record element + * @method + * @param {object} attributes + * @param {string} [attributes.action] + * @param {string} [attributes.method] + * @param {string} [attributes.fileFormat] + * @param {boolean} [attributes.redirect] + * @param {number} [attributes.timeout] + * @param {number} [attributes.maxLength] + * @param {boolean} [attributes.playBeep] + * @param {string} [attributes.finishOnKey] + * @param {boolean} [attributes.recordSession] + * @param {boolean} [attributes.startOnDialAnswer] + * @param {string} [attributes.transcriptionType] + * @param {string} [attributes.transcriptionUrl] + * @param {string} [attributes.transcriptionMethod] + * @param {string} [attributes.callbackUrl] + * @param {string} [attributes.callbackMethod] + */ + addRecord: function (attributes) { + return this.add(new Record(Response),'', attributes); + }, + + /** + * Add a Redirect element + * @method + * @param {string} body + * @param {object} attributes + * @param {string} [attributes.method] + */ + addRedirect: function (body, attributes) { + return this.add(new Redirect(Response), body, attributes); + }, + + /** + * Add a Speak element + * @method + * @param {string} body + * @param {object} attributes + * @param {string} [attributes.voice] + * @param {string} [attributes.language] + * @param {number} [attributes.loop] + */ + addSpeak: function (body, attributes) { + return this.add(new Speak(Response), body, attributes); + }, + + /** + * Add a Wait element + * @method + * @param {object} attributes + * @param {number} [attributes.length] + * @param {boolean} [attributes.silence] + * @param {number} [attributes.minSilence] + * @param {boolean} [attributes.beep] + */ + addWait: function (attributes) { + return this.add(new Wait(Response), '', attributes); + }, + + /** + * Add a DTMF element + * @method + * @param {string} body + * @param {object} attributes + * @param {boolean} [attributes.async] + */ + addDTMF: function (body, attributes) { + return this.add(new DTMF(Response), body, attributes); + }, + + toXML: function () { + return this.elem.toString(); + } +}; + +/** + * Conference element + * @constructor + */ +function Conference(Response) { + this.element = 'Conference'; + this.valid_attributes = ['muted', 'beep', 'startConferenceOnEnter', + 'endConferenceOnExit', 'waitSound', 'enterSound', 'exitSound', + 'timeLimit', 'hangupOnStar', 'maxMembers', 'record','recordWhenAlone', + 'recordFileFormat', 'action', 'method', 'redirect', + 'digitsMatch', 'callbackUrl', 'callbackMethod', 'stayAlone', + 'floorEvent', 'transcriptionType', 'transcriptionUrl', + 'transcriptionMethod', 'relayDTMF']; + this.nestables = []; +} +util.inherits(Conference, Response); + +/** + * Number element + * @constructor + */ +function Number(Response) { + this.element = 'Number'; + this.valid_attributes = ['sendDigits', 'sendOnPreanswer', 'sendDigitsMode']; + this.nestables = []; +} +util.inherits(Number, Response); + +/** + * User element + * @constructor + */ +function User(Response) { + this.element = 'User'; + this.nestables = []; + this.valid_attributes = ['sendDigits', 'sendOnPreanswer', 'sipHeaders']; +} +util.inherits(User, Response); + +/** + * Dial element + * @constructor + */ +function Dial(Response) { + this.element = 'Dial'; + this.valid_attributes = ['action', 'method', 'timeout', 'hangupOnStar', + 'timeLimit', 'callerId', 'callerName', 'confirmSound', + 'dialMusic', 'confirmKey', 'redirect', 'callbackUrl', + 'callbackMethod', 'digitsMatch', 'digitsMatchBLeg', 'sipHeaders']; + this.nestables = ['Number', 'User']; +} +util.inherits(Dial, Response); + +/** + * GetDigits element + * @constructor + */ +function GetDigits(Response) { + this.element = 'GetDigits'; + this.valid_attributes = ['action', 'method', 'timeout', 'digitTimeout', + 'finishOnKey', 'numDigits', 'retries', 'invalidDigitsSound', + 'validDigits', 'playBeep', 'redirect', 'log']; + this.nestables = ['Speak', 'Play', 'Wait']; +} +util.inherits(GetDigits, Response); + +/** + * Hangup element + * @constructor + */ +function Hangup(Response) { + this.element = 'Hangup'; + this.valid_attributes = ['schedule', 'reason']; + this.nestables = []; +} +util.inherits(Hangup, Response); + +/** + * Message element + * @constructor + */ +function Message(Response) { + this.element = 'Message'; + this.nestables = []; + this.valid_attributes = ['src', 'dst', 'type', 'callbackUrl', + 'callbackMethod']; +} +util.inherits(Message, Response); + +/** + * Play element + * @constructor + */ +function Play(Response) { + this.element = 'Play'; + this.valid_attributes = ['loop']; + this.nestables = []; +} +util.inherits(Play, Response); + +/** + * PreAnswer element + * @constructor + */ +function PreAnswer(Response) { + this.element = 'PreAnswer'; + this.valid_attributes = []; + this.nestables = ['Play', 'Speak', 'GetDigits', 'Wait', 'Redirect', + 'Message', 'DTMF']; +} +util.inherits(PreAnswer, Response); + +/** + * Record element + * @constructor + */ +function Record(Response) { + this.element = 'Record'; + this.nestables = []; + this.valid_attributes = ['action', 'method', 'timeout', 'finishOnKey', + 'maxLength', 'playBeep', 'recordSession', + 'startOnDialAnswer', 'redirect', 'fileFormat', + 'callbackUrl', 'callbackMethod', 'transcriptionType', + 'transcriptionUrl', 'transcriptionMethod']; +} +util.inherits(Record, Response); + +/** + * Redirect element + * @constructor + */ +function Redirect(Response) { + this.element = 'Redirect'; + this.valid_attributes = ['method']; + this.nestables = []; +} +util.inherits(Redirect, Response); + +/** + * Speak element + * @constructor + */ +function Speak(Response) { + this.element = 'Speak'; + this.valid_attributes = ['voice', 'language', 'loop']; + this.nestables = []; +} +util.inherits(Speak, Response); + +/** + * Wait element + * @constructor + */ +function Wait(Response) { + this.element = 'Wait'; + this.valid_attributes = ['length', 'silence', 'min_silence', 'minSilence', 'beep']; + this.nestables = []; +} +util.inherits(Wait, Response); + +/** + * DTMF element + * @constructor + */ +function DTMF(Response) { + this.element = 'DTMF'; + this.nestables = []; + this.valid_attributes = ['digits', 'async']; +} + +util.inherits(DTMF, Response); + diff --git a/lib/utils/security.js b/lib/utils/security.js new file mode 100644 index 00000000..53539f86 --- /dev/null +++ b/lib/utils/security.js @@ -0,0 +1,31 @@ +//@flow +const utf8 = require('utf8'); +const buildUrl = require('build-url'); +const base64 = require('base-64'); +import * as parser from 'uri-parser'; +import crypto from 'crypto'; +import _ from 'lodash'; + +export function computeOldSignature(authId: string, uri: string, params: {[string]: string}): string { + const joinedParams = uri + _.join(_.map(_.sortBy(_.toPairs(params)), item => item[0] + item[1]), ''); + console.log(joinedParams); + return crypto.createHmac('sha1', authId).update(joinedParams).digest('base64'); +} + +export function verifyOldSignature(authId: string, uri: string, params: {[string]: string}, signature: string): boolean { + return computeOldSignature(authId, uri, params) === signature; +} + +export function validateSignature(uri: string, nonce: string, signature: string, auth_token: string) { + nonce = utf8.encode(nonce); + signature = utf8.encode(signature); + auth_token = utf8.encode(auth_token); + uri = utf8.encode(uri); + let parsed_uri = parser.parse(uri); + let url_protocol = parsed_uri.protocol == '' ? 'http://' : parsed_uri.protocol+'://'; + let base_url = buildUrl(url_protocol+parsed_uri.host, { path: parsed_uri.path }); + let hmac = crypto.createHmac('sha256', auth_token); + let hmacBytes = base64.decode(hmac.update(base_url+nonce).digest('base64')); + let authentication_string = base64.encode(hmacBytes); + return authentication_string == signature; +} diff --git a/lib/version.js b/lib/version.js new file mode 100644 index 00000000..e69de29b diff --git a/package.json b/package.json new file mode 100644 index 00000000..f560f53d --- /dev/null +++ b/package.json @@ -0,0 +1,70 @@ +{ + "name": "plivo", + "version": "4.0.0-beta.1", + "description": "A Node.js SDK to make voice calls & send SMS using Plivo and to generate Plivo XML", + "homepage": "https://github.com/plivo/plivo-node", + "files": [ + "dist" + ], + "main": "dist/rest/client.js", + "keywords": [ + "plivo", + "plivo xml", + "rest", + "api", + "telephony" + ], + "author": "Plivo SDKs Team", + "license": "MIT", + "readmeFilename": "README.md", + "devDependencies": { + "babel-core": "^6.11.4", + "babel-eslint": "^6.1.2", + "babel-preset-es2015": "6.9.0", + "babel-preset-flow": "^6.23.0", + "babel-register": "^6.9.0", + "del": "^2.0.2", + "eslint": "^3.19.0", + "eslint-config-xo-space": "^0.15.0", + "eslint-plugin-babel": "^3.3.0", + "eslint-plugin-flowtype": "^2.33.0", + "flow-bin": "^0.47.0", + "gulp": "^3.9.0", + "gulp-babel": "^6.1.2", + "gulp-babel-istanbul": "^1.6.0", + "gulp-coveralls": "^0.1.0", + "gulp-eslint": "^3.0.1", + "gulp-exclude-gitignore": "^1.0.0", + "gulp-istanbul": "^1.0.0", + "gulp-line-ending-corrector": "^1.0.1", + "gulp-mocha": "^3.0.1", + "gulp-nsp": "^2.1.0", + "gulp-plumber": "^1.0.0", + "isparta": "^4.0.0", + "sinon": "^2.1.0" + }, + "eslintConfig": { + "extends": "xo-space", + "env": { + "mocha": true + } + }, + "repository": { + "type": "git", + "url": "git://github.com/plivo/plivo-node.git" + }, + "scripts": { + "prepublish": "gulp prepublish", + "test": "gulp" + }, + "dependencies": { + "lodash": "^4.17.4", + "querystring": "^0.2.0", + "request": "^2.81.0", + "xmlbuilder": "^9.0.1", + "base-64": "^0.1.0", + "build-url": "^1.0.10", + "uri-parser": "^1.0.0", + "utf8": "^2.1.2" + } +} diff --git a/test/accounts.js b/test/accounts.js new file mode 100644 index 00000000..a015d2b4 --- /dev/null +++ b/test/accounts.js @@ -0,0 +1,92 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import {Client} from '../lib/rest/client-test'; +import {PlivoGenericResponse} from '../lib/base.js'; + +let client = new Client('sampleid', 'sammpletoken', 'sampleproxy'); + +describe('Account', function () { + it('should getAccount', function () { + return client.accounts.get() + .then(function(account) { + assert.equal(account.authId, 'MANWVLYTK4ZWU1YTY4ZT') + assert.equal(account.id, 'MANWVLYTK4ZWU1YTY4ZT') + }) + }); + + it('should update Account via interface', function () { + return client.accounts.get() + .then(function(account){ + return account.update({ + name: 'name', + city: 'city', + address: 'address' + }) + .then(function(account) { + assert.equal(account.name, 'name') + }) + }) + }); + + it('should update Account', function () { + return client.accounts.update({ + name: 'name', + city: 'city', + address: 'address' + }) + .then(function(account) { + assert.equal(account.name, 'name') + }) + }); + + it('should create subAccount via interface', function() { + return client.subAccounts.create('Test Subaccount', true); + }) + + it('should get subAccount by id via interface', function () { + return client.subAccounts.get(1) + .then(function(subaccount) { + assert.equal(subaccount.authId, 1) + }) + }); + + it('should update subAccount via interface', function () { + return client.subAccounts.update(1, 'name', true) + .then(function(account) { + assert.equal(account.name, 'name') + }) + }); + + it('should update subAccount', function () { + return client.subAccounts.get(1) + .then(function(subaccount){ + return subaccount.update('name', true) + }) + .then(function(account) { + assert.equal(account.name, 'name') + }) + }); + + it('list subAccounts', function () { + return client.subAccounts.list() + .then(function(accounts) { + assert.equal(accounts.length, 2) + }) + }); + + it('delete subAccounts', function () { + return client.subAccounts.get(1) + .then(function(subaccount){ + return subaccount.delete() + }) + .then(function(account) { + assert.equal(account, true) + }) + }); + it('delete subAccounts via interface', function () { + return client.subAccounts.delete(1) + .then(function(accounts) { + assert.equal(accounts, true) + }) + }); +}); diff --git a/test/applications.js b/test/applications.js new file mode 100644 index 00000000..a5e6fa02 --- /dev/null +++ b/test/applications.js @@ -0,0 +1,69 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import {Client} from '../lib/rest/client-test'; +import {PlivoGenericResponse} from '../lib/base.js'; + +let client = new Client('sampleid', 'sammpletoken', 'sampleproxy'); + +describe('Application', function () { + it('should get Application', function () { + return client.applications.get(1) + .then(function(application) { + assert.equal(application.id, 1) + }) + }); + + it('list Applications', function () { + return client.applications.list() + .then(function(applications) { + assert.equal(applications.length, 2) + }) + }); + + it('should create application via interface', function () { + return client.applications.create('appName') + .then(function(application){ + assert.equal(application.message, 'created') + }) + }); + + it('should update Application via interface', function () { + return client.applications.update(1, {answer_url: 'answerUrl'}) + .then(function(application) { + assert.equal(application.answer_url, 'answerUrl') + }) + }); + + it('should throw error - id is required via interface', function () { + return client.applications.update(null, {answer_url: 'answerUrl'}) + .catch(function(err){ + assert.equal(err.message, 'Missing mandatory field: id') + }) + }); + + it('should update Application', function () { + return client.applications.get(1) + .then(function(application) { + return application.update({answer_url: 'answerUrl'}) + }) + .then(function(application){ + assert.equal(application.answer_url, 'answerUrl') + }) + }); + + it('delete application', function () { + return client.applications.get(1) + .then(function(application){ + return application.delete() + }) + .then(function(status) { + assert.equal(status, true) + }) + }); + it('delete application via interface', function () { + return client.applications.delete(1) + .then(function(status) { + assert.equal(status, true) + }) + }); +}); diff --git a/test/calls.js b/test/calls.js new file mode 100644 index 00000000..b4424a4b --- /dev/null +++ b/test/calls.js @@ -0,0 +1,292 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import {Client} from '../lib/rest/client-test'; +import {PlivoGenericResponse} from '../lib/base.js'; + +let client = new Client('sampleid', 'sammpletoken', 'sampleproxy'); + +describe('client', function () { + let authId, authToken + + it('should have config object!', function () { + assert('object', typeof new Client('sampleid', 'sammpletoken')); + }); + + it('throw error if authid is not provied!', function () { + try { + new Client(authId, 'sammpletoken') + } catch (err) { + assert.equal(err, 'Please provide authId') + } + }); + + it('throw error if authToken is not provied!', function () { + try { + new Client('sampleauthid', authToken) + } catch (err) { + assert.equal(err, 'Please provide authToken') + } + }); + + +}) +describe('calls', function () { + + it('should have calls object!', function () { + assert('object', typeof client.calls); + }); + + it('should have create method!', function () { + assert('function', typeof client.calls.create); + }); + + it('should make call!', function () { + return client.calls.create('+9100000000', '+920000000', 'http://google.com') + .then(function(call) { + assert.equal(call.message, 'call fired') + }) + }); + it('should get list of calls!', function (done) { + client.calls.list() + .then(function(calls){ + assert.equal(calls.length, 0) + done() + }) + .catch(function(e) { + assert.equal(0, 0) + }) + }); + it('should get call by id!', function (done) { + client.calls.get(1) + .then(function(call){ + assert.equal(call.id, 1) + done() + }) + }); + describe('transfer', function () { + it('should transfer call!', function (done) { + client.calls.get(1) + .then(function(call){ + return call.transfer() + }) + .then(function(call) { + assert.equal(call.id, 5) + done() + }) + }); + }); + + it('should transfer call via plivo interface!', function (done) { + client.calls.transfer(1, {}) + .then(function(call) { + assert.equal(call.id, 5) + done() + }) + }); + + describe('Hangup', function () { + it('should hangup call!', function (done) { + client.calls.get(1) + .then(function(call){ + return call.hangup() + }) + .then(function(call) { + assert(call) + done() + }) + }); + it('should cancel call!', function () { + return client.calls.get(1) + .then(function(call){ + return call.cancel(); + }) + .then(function(call) { + assert(call); + }) + }); + it('should hangup call via plivo interface!', function (done) { + client.calls.hangup(1) + .then(function(call) { + assert(call) + done() + }) + }); + }); + describe('Record', function () { + it('should record call!', function (done) { + client.calls.get(1) + .then(function(call){ + return call.record({}) + }) + .then(function(recordDetail) { + assert.equal(recordDetail.message, 'call recording started') + done() + }) + }); + it('should record call via plivo interface!', function (done) { + client.calls.record(1, {}) + .then(function(recordDetail) { + assert.equal(recordDetail.message, 'call recording started') + done() + }) + }); + + it('should stop recording call!', function (done) { + client.calls.get(1) + .then(function(call){ + return call.stopRecording({}) + }) + .then(function(recordDetail) { + assert(recordDetail instanceof PlivoGenericResponse) + done() + }) + }); + + it('should stop recording call via plivo interface!', function (done) { + client.calls.stopRecording(1, {}) + .then(function(recordDetail) { + assert(recordDetail instanceof PlivoGenericResponse) + done() + }) + }); + }); + + describe('DTMF', function () { + it('should send digits', function () { + return client.calls.sendDigits('1', '123'); + }); + }); + + describe('Play', function () { + it('should throw error for url!', function (done) { + client.calls.get(1) + .then(function(call) { + return call.playMusic() + }) + .catch(function(err){ + assert.equal(err.message, 'Missing mandatory field: urls, urls should be string.') + done() + }) + }); + + it('should throw error for url via plivo interface!', function (done) { + client.calls.playMusic(1) + .catch(function(err){ + assert.equal(err.message, 'Missing mandatory field: urls, urls should be string.') + done() + }) + }); + + it('play audio file for call', function (done) { + client.calls.get(1) + .then(function(call) { + return call.playMusic('http://localhost') + }) + .then(function(resp){ + assert.equal(resp.message, 'play started') + done() + }) + }); + + it('play audio file for call via plivo interface!', function (done) { + client.calls.playMusic(1, 'http://localhost') + .then(function(resp){ + assert.equal(resp.message, 'play started') + done() + }) + }); + it('stop playing audio file for call', function (done) { + client.calls.get(1) + .then(function(call) { + return call.stopPlayingMusic() + }) + .then(function(resp){ + assert(resp instanceof PlivoGenericResponse) + done() + }) + }); + + it('stop playing audio file for call via plivo interface!', function (done) { + client.calls.stopPlayingMusic(1) + .then(function(resp){ + assert(resp instanceof PlivoGenericResponse) + done() + }) + }); + }); + + describe('Speak', function () { + it('should throw error for text!', function (done) { + client.calls.get(1) + .then(function(call) { + return call.speakText() + }) + .catch(function(err){ + assert.equal(err.message, 'Missing mandatory field: text, text should be string.') + done() + }) + }); + + it('play text for call', function (done) { + client.calls.get(1) + .then(function(call) { + return call.speakText('this is test') + }) + .then(function(resp){ + assert.equal(resp.message, 'speak started') + done() + }) + }); + + it('play text for call via plivo interface!', function (done) { + client.calls.speakText(1, 'this is test') + .then(function(resp){ + assert.equal(resp.message, 'speak started') + done() + }) + }); + it('stop playing text for call', function (done) { + client.calls.get(1) + .then(function(call) { + return call.stopSpeakingText() + }) + .then(function(resp){ + assert(resp instanceof PlivoGenericResponse) + assert.equal(resp.message, 'speak stopped') + done() + }) + }); + + it('stop playing text for call via plivo interface!', function (done) { + client.calls.stopSpeakingText(1) + .then(function(resp){ + assert.equal(resp.message, 'speak stopped') + done() + }) + }); + }); + + // it('should make call!', function () { + // client.calls.make('', '', 'http://localhost:8000') + // .then(function(call) { + // assert('string', typeof call.message) + // return call.hangup(call.request_uuid) + // }) + // .then(function(status) { + // assert(true, status) + // }) + // }); + describe('LiveCall', function () { + it('should get a livecall', function () { + return client.calls.getLiveCall('6653422-91b6-4716-9fad-9463daaeeec2') + .then(function (resp) { + console.log(resp); + assert.equal(resp.callUuid, '6653422-91b6-4716-9fad-9463daaeeec2'); + }); + }); + + it('should list livecalls', function () { + return client.calls.listLiveCalls(); + }); + }) +}); diff --git a/test/conferences.js b/test/conferences.js new file mode 100644 index 00000000..6541f41f --- /dev/null +++ b/test/conferences.js @@ -0,0 +1,258 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import {Client} from '../lib/rest/client-test'; +import {PlivoGenericResponse} from '../lib/base.js'; + +let client = new Client('sampleid', 'sammpletoken', 'sampleproxy'); + +describe('Conference', function () { + describe('Via interface', function () { + it('should get conference', function () { + return client.conferences.get('MyConf') + .then(function(conference) { + assert.equal(conference.id, 'MyConf') + }) + }); + + it('should get all conferences', function () { + return client.conferences.list() + .then(function(conferences) { + assert.equal(conferences[0].name, 'My Conf Room' ) + }) + }); + + it('should Hangup All Conferences', function () { + return client.conferences.hangupAll() + .then(function(response) { + assert.equal(response.message,'all conferences hung up') + }) + }); + + it('should Hangup Conference', function () { + return client.conferences.hangup('MyConf') + .then(function(status) { + assert.equal(status, true) + }) + }); + + it('should Hangup Member', function () { + return client.conferences.hangupMember('MyConf', 1) + .then(function(response) { + assert.equal(response.message, 'hangup') + }) + }); + + it('should Kick Member', function () { + return client.conferences.kickMember('MyConf', 1) + .then(function(response) { + assert.equal(response.message, 'kicked') + }) + }); + + it('should Mute Member', function () { + return client.conferences.muteMember('MyConf', 1) + .then(function(response) { + assert.equal(response.message, 'muted') + }) + }); + + it('should Unmute Member', function () { + return client.conferences.unmuteMember('MyConf', 1) + .then(function(response) { + assert.equal(response instanceof PlivoGenericResponse, true) + }) + }); + + it('should Deaf Member', function () { + return client.conferences.deafMember('MyConf', 1) + .then(function(response) { + assert.equal(response.message, 'deaf') + }) + }); + + it('should undeaf Member', function () { + return client.conferences.undeafMember('MyConf', 1) + .then(function(response) { + assert.equal(response instanceof PlivoGenericResponse, true) + }) + }); + + it('should play Audio to Member', function () { + return client.conferences.playAudioToMember('MyConf', 1, 'http://localhost') + .then(function(response) { + assert.equal(response.message, 'play queued into conference') + }) + }); + + it('should stop playing Audio to Member', function () { + return client.conferences.stopPlayingAudioToMember('MyConf', 1) + .then(function(response) { + assert.equal(response.message, 'playing in conference stopped') + }) + }); + + it('should play text to Member', function () { + return client.conferences.speakTextToMember('MyConf', 1, 'text') + .then(function(response) { + assert.equal(response.message, 'speak queued into conference') + }) + }); + + it('should stop playing text to Member', function () { + return client.conferences.stopSpeakingTextToMember('MyConf', 1) + .then(function(response) { + assert.equal(response.message, 'speak stopped') + }) + }); + + it('should stop record conference', function () { + return client.conferences.stopRecording('MyConf') + .then(function(response) { + assert.equal(response instanceof PlivoGenericResponse, true) + }) + }); + + it('should record conference', function () { + return client.conferences.record('MyConf') + .then(function(response) { + assert.equal(response.message, 'conference recording started') + }) + }); + }); + + describe('Via Conference Object', function () { + + it('should Hangup Conference', function () { + return client.conferences.get('MyConf') + .then(function(conference) { + return conference.hangup() + }) + .then(function(status) { + assert.equal(status, true) + }) + }); + + it('should Hangup Member', function () { + return client.conferences.get('MyConf') + .then(function(conference) { + return conference.hangupMember(1) + }) + .then(function(response) { + assert.equal(response.message, 'hangup') + }) + }); + + it('should Kick Member', function () { + return client.conferences.get('MyConf') + .then(function(conference) { + return conference.kickMember(1) + }) + .then(function(response) { + assert.equal(response.message, 'kicked') + }) + }); + + it('should Mute Member', function () { + return client.conferences.get('MyConf') + .then(function(conference) { + return conference.muteMember(1) + }) + .then(function(response) { + assert.equal(response.message, 'muted') + }) + }); + + it('should Unmute Member', function () { + return client.conferences.get('MyConf') + .then(function(conference) { + return conference.unmuteMember(1) + }) + .then(function(response) { + assert.equal(response instanceof PlivoGenericResponse, true) + }) + }); + + it('should Deaf Member', function () { + return client.conferences.get('MyConf') + .then(function(conference) { + return conference.deafMember(1) + }) + .then(function(response) { + assert.equal(response.message, 'deaf') + }) + }); + + it('should undeaf Member', function () { + return client.conferences.get('MyConf') + .then(function(conference) { + return conference.undeafMember(1) + }) + .then(function(response) { + assert.equal(response instanceof PlivoGenericResponse, true) + }) + }); + + it('should play Audio to Member', function () { + return client.conferences.get('MyConf') + .then(function(conference) { + return conference.playAudioToMember(1, 'http://localhost') + }) + .then(function(response) { + assert.equal(response.message, 'play queued into conference') + }) + }); + + it('should stop playing Audio to Member', function () { + client.conferences.get('MyConf') + .then(function(conference) { + return conference.stopPlayingAudioToMember(1) + }) + .then(function(response) { + assert.equal(response.message, 'playing in conference stopped') + }) + }); + + it('should play text to Member', function () { + return client.conferences.get('MyConf') + .then(function(conference) { + return conference.speakTextToMember(1, 'text') + }) + .then(function(response) { + assert.equal(response.message, 'speak queued into conference') + }) + }); + + it('should stop playing text to Member', function () { + return client.conferences.get('MyConf') + .then(function(conference) { + return conference.stopSpeakingTextToMember(1) + }) + .then(function(response) { + assert.equal(response.message, 'speak stopped') + }) + }); + + it('should stop record conference', function () { + return client.conferences.get('MyConf') + .then(function(conference) { + return conference.stopRecording() + }) + .then(function(response) { + assert.equal(response instanceof PlivoGenericResponse, true) + }) + }); + + it('should record conference', function () { + return client.conferences.get('MyConf') + .then(function(conference) { + return conference.record(); + }) + .then(function(response) { + assert.equal(response.message, 'conference recording started') + }) + }); + }); + + + +}); diff --git a/test/endpoints.js b/test/endpoints.js new file mode 100644 index 00000000..900c91fa --- /dev/null +++ b/test/endpoints.js @@ -0,0 +1,75 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import {Client} from '../lib/rest/client-test'; +import {PlivoGenericResponse} from '../lib/base.js'; + +let client = new Client('sampleid', 'sammpletoken', 'sampleproxy'); + +describe('Endpoint', function () { + it('should get Endpoint', function () { + return client.endpoints.get(1) + .then(function(endpoint) { + assert.equal(endpoint.id, 1) + }) + }); + + it('list Endpoints', function () { + return client.endpoints.list() + .then(function(endpoints) { + assert.equal(endpoints.length, 2) + }) + }); + + it('should create endpoint via interface', function () { + return client.endpoints.create('username', 'password', 'alias') + .then(function(endpoint){ + assert.equal(endpoint.message, 'created') + }) + }); + + it('should update endpoint via interface', function () { + return client.endpoints.update(1, { + username: 'username' + }) + .then(function(endpoint) { + assert.equal(endpoint.username, 'username') + }) + }); + + it('should throw error - id is required via interface', function () { + return client.endpoints.update(null, { + username: 'username' + }) + .catch(function(err){ + assert.equal(err.message, 'Missing mandatory field: id') + }) + }); + + it('should update endpoint', function () { + return client.endpoints.get(1) + .then(function(endpoint) { + return endpoint.update({ + username: 'username' + }) + }) + .then(function(endpoint){ + assert.equal(endpoint.username, 'username') + }) + }); + + it('delete endpoint', function () { + return client.endpoints.get(1) + .then(function(endpoint){ + return endpoint.delete() + }) + .then(function(status) { + assert.equal(status, true) + }) + }); + it('delete endpoint via interface', function () { + return client.endpoints.delete(1) + .then(function(status) { + assert.equal(status, true) + }) + }); +}); diff --git a/test/messages.js b/test/messages.js new file mode 100644 index 00000000..b24f4eaf --- /dev/null +++ b/test/messages.js @@ -0,0 +1,52 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import {Client} from '../lib/rest/client-test'; +import {PlivoGenericResponse} from '../lib/base.js'; + +let client = new Client('sampleid', 'sammpletoken', 'sampleproxy'); + +describe('message', function () { + it('should get message', function () { + return client.messages.get(1) + .then(function(message) { + assert.equal(message.id, 1) + }) + }); + + it('list messages', function () { + return client.messages.list() + .then(function(messages) { + assert.equal(messages.length, 2) + }) + }); + + it('should create message via interface', function () { + return client.messages.create('src', 'dst', 'text') + .then(function(message){ + assert.equal(message.message, 'message(s) queued') + }) + }); + + it('should send message via interface', function () { + return client.messages.send('src', 'dst', 'text') + .then(function(message){ + assert.equal(message.message, 'message(s) queued') + }) + }); + + + it('should throw error - id is required via interface', function () { + return client.messages.get() + .catch(function(err){ + assert.equal(err.message, 'Missing mandatory field: id') + }) + }); + + it('should throw error - src is required via interface', function () { + return client.messages.send(null, 'dst', 'text') + .catch(function(err){ + assert.equal(err.message, 'Missing mandatory field: src') + }) + }); + +}); diff --git a/test/numbers.js b/test/numbers.js new file mode 100644 index 00000000..e42a9b80 --- /dev/null +++ b/test/numbers.js @@ -0,0 +1,93 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import {Client} from '../lib/rest/client-test'; +import {PlivoGenericResponse} from '../lib/base.js'; + +let client = new Client('sampleid', 'sammpletoken', 'sampleproxy'); + +describe('NumberInterface', function () { + it('Get Details of a Rented Number', function () { + return client.numbers.get('+919999999990') + .then(function(number) { + assert.equal(number.id, '+919999999990') + }) + }); + + it('List All Rented Numbers', function () { + return client.numbers.list() + .then(function(numbers) { + assert.equal(numbers.length, 1) + }) + }); + + it('add own number', function () { + return client.numbers.addOwnNumber('+919999999990', 'carrier', 'region') + .then(function(numbers) { + assert.equal(numbers.message, 'changed') + }) + }); + + it('should throw error for number', function () { + return client.numbers.addOwnNumber(null, 'carrier', 'region') + .catch(function(err) { + assert.equal(err.message, 'Missing mandatory field: numbers') + }) + }); + + it('edit a number', function () { + return client.numbers.update('+919999999990', 'appid', 'subaccount', 'alias') + .then(function(numbers) { + assert.equal(numbers.message, 'changed') + }) + }); + + it('should throw error for number', function () { + return client.numbers.update(null, 'appid', 'subaccount', 'alias') + .catch(function(err) { + assert.equal(err.message, 'Missing mandatory field: number') + }) + }); + + it('unrent a number', function () { + return client.numbers.unrent('+919999999990') + .then(function(numbers) { + assert.equal(numbers, true) + }) + }); + + it('should throw error for number for unrent', function () { + return client.numbers.unrent() + .catch(function(err) { + assert.equal(err.message, 'Missing mandatory field: number') + }) + }); + + it('Search for New Numbers', function () { + return client.numbers.search('US') + .then(function(numbers) { + assert.equal(numbers.length, 2) + }) + }); + + it('should throw error country_iso', function () { + return client.numbers.search() + .catch(function(err) { + assert.equal(err.message, 'Missing mandatory field: country_iso') + }) + }); + + it('Buy Number', function () { + return client.numbers.buy('+919999999990', 'appId') + .then(function(numbers) { + assert.equal(numbers.status, 'fulfilled') + }) + }); + + it('should throw error for number in buy', function () { + return client.numbers.buy() + .catch(function(err) { + assert.equal(err.message, 'Missing mandatory field: number') + }) + }); + +}); diff --git a/test/pricings.js b/test/pricings.js new file mode 100644 index 00000000..043f9dd2 --- /dev/null +++ b/test/pricings.js @@ -0,0 +1,23 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import {Client} from '../lib/rest/client-test'; +import {PlivoGenericResponse} from '../lib/base.js'; + +let client = new Client('sampleid', 'sammpletoken', 'sampleproxy'); + +describe('PricingInterface', function () { + it('should get pricings via interface', function () { + return client.pricings.get('US') + .then(function(pricings) { + assert.equal(pricings.countryIso, 'US'); + }) + }); + + it('throw error for country iso', function () { + return client.pricings.get() + .catch(function(err) { + assert.equal(err.message, 'Missing mandatory field: country_iso') + }) + }); + +}); diff --git a/test/recordings.js b/test/recordings.js new file mode 100644 index 00000000..d3ccbd1a --- /dev/null +++ b/test/recordings.js @@ -0,0 +1,54 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import {Client} from '../lib/rest/client-test'; +import {PlivoGenericResponse} from '../lib/base.js'; + +let client = new Client('sampleid', 'sammpletoken', 'sampleproxy'); + +describe('RecordingInterface', function () { + it('should get recording via interface', function () { + return client.recordings.get(1) + .then(function(recording) { + assert.equal(recording.id, 1) + }) + }); + + it('should get all recording via interface', function () { + return client.recordings.list() + .then(function(recording) { + assert.equal(recording.length, 2) + }) + }); + + it('should delete recording', function () { + return client.recordings.delete(1) + .then(function(status) { + assert.equal(status, true) + }) + }); + + it('should delete recording via interface', function () { + return client.recordings.get(1) + .then(function(recording){ + return recording.delete() + }) + .then(function(status) { + assert.equal(status, true) + }) + }); + + it('throw error for id', function () { + return client.recordings.get() + .catch(function(err) { + assert.equal(err.message, 'Missing mandatory field: id') + }) + }); + + it('throw error for id', function () { + return client.recordings.delete() + .catch(function(err) { + assert.equal(err.message, 'Missing mandatory field: id') + }) + }); + +}); diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 00000000..5c03e0c2 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,40 @@ +import assert from 'assert'; +import sinon from 'sinon'; + +import {computeOldSignature, verifyOldSignature, validateSignature} from '../lib/utils/security'; + +describe('Security', function () { + it('should compute signature correctly', function () { + assert.equal(computeOldSignature('MAXXXXXXXXXXXXXXXXXX', 'http://foo.com/answer/', { + CallUUID: '97ceeb52-58b6-11e1-86da-77300b68f8bb', + Duration: '300' + }), 'EJEt0ELanhr8hjMPIJnLNLex0dE='); + }); + + it('should encode special characters correctly', function () { + assert.equal(computeOldSignature('MAXXXXXXXXXXXXXXXXXX', 'http://foo.com/answer/', { + a: "1 2" + }), 'n3Xfo4u+vRFyl3gsH8B0qDUIK5g='); + }); + + it('should fail for wrong signature', function () { + assert.equal(verifyOldSignature('MAXXXXXXXXXXXXXXXXXX', 'http://foo.com/answer/', { + CallUUID: '97ceeb52-58b6-11e1-86da-77300b68f8bb', + Duration: '300' + }, 'EJEt0ELanhr8hjMPIJnLNLef0dE='), false); + }); + + it('should pass for correct url,auth_token,nonce and signature', function () { + assert.equal( + validateSignature('https://answer.url','12345','ehV3IKhLysWBxC1sy8INm0qGoQYdYsHwuoKjsX7FsXc=','my_auth_token'), + true + ); + }); + + it('should fail for wrong url,auth_token,nonce and signature', function () { + assert.equal( + validateSignature('https://answer.url','123456','ehV3IKhLysWBxC1sy8INm0qGoQYdYsHwuoKjsX7FsXc=','my_auth_tokens'), + false + ); + }); +}); diff --git a/test/xml.js b/test/xml.js new file mode 100644 index 00000000..9b935fde --- /dev/null +++ b/test/xml.js @@ -0,0 +1,27 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import {Response} from '../lib/utils/plivoxml'; + +describe('PlivoXML', function () { + it('should work', function () { + const response = new Response(); + response.addPreAnswer(); + response.addRecord(); + response.addHangup(); + response.addSpeak('text'); + response.addWait(); + response.addDTMF('123'); + response.addConference('test'); + response.addRedirect('url'); + response.addGetDigits(); + response.addPlay('url'); + const dial = response.addDial(); + dial.addNumber('123'); + dial.addUser('sip:test@sip.plivo.com'); + response.addMessage('∫test', { + src: '123', + dst: '456', + }); + assert.equal('text123testurlurl123sip:test@sip.plivo.com∫test', response.toXML()); + }); +});