├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md └── workflows │ └── release.yml ├── .gitignore ├── .npmignore ├── .nycrc ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── MAINTENANCE.md ├── README.md ├── examples ├── README.md ├── config.js ├── ex-change-api-host.js ├── ex-change-rest-host.js ├── ex-check-balance.js ├── ex-check-verification.js ├── ex-control-verification.js ├── ex-create-secret.js ├── ex-create-update-delete-app.js ├── ex-create-v1-application.js ├── ex-create-v2-application.js ├── ex-delete-application.js ├── ex-dtmf-to-call.js ├── ex-get-calls.js ├── ex-get-full-pricing.js ├── ex-get-phone-pricing-number.js ├── ex-get-phone-pricing.js ├── ex-get-prefix-pricing.js ├── ex-get-pricing-number.js ├── ex-get-pricing.js ├── ex-get-secret.js ├── ex-get-v1-application.js ├── ex-get-v1-applications.js ├── ex-get-v2-application.js ├── ex-get-v2-applications.js ├── ex-keyfile-creds.js ├── ex-list-secrets.js ├── ex-make-call-ncco.js ├── ex-make-call.js ├── ex-make-multiple-calls.js ├── ex-media-delete.js ├── ex-media-download.js ├── ex-media-get.js ├── ex-media-search.js ├── ex-media-update.js ├── ex-media-upload.js ├── ex-number-buy.js ├── ex-number-cancel.js ├── ex-number-get.js ├── ex-number-insight-advanced-async.js ├── ex-number-insight-advanced-sync.js ├── ex-number-insight-basic.js ├── ex-number-insight-standard.js ├── ex-number-pricing.js ├── ex-number-search.js ├── ex-number-update.js ├── ex-redact-transaction.js ├── ex-revoke-secret.js ├── ex-search-verification.js ├── ex-send-binary-sms.js ├── ex-send-psd2-verification-with-workflow.js ├── ex-send-psd2-verification.js ├── ex-send-shortcode-2fa.js ├── ex-send-shortcode-alert.js ├── ex-send-signed-sms.js ├── ex-send-sms.js ├── ex-send-tts.js ├── ex-send-verification-with-workflow.js ├── ex-send-verification.js ├── ex-send-wap-message.js ├── ex-stream-to-call.js ├── ex-talk-to-call.js ├── ex-update-v1-application.js ├── ex-update-v2-application.js ├── ex-verify-signed-sms-without-instance.js ├── ex-verify-signed-sms.js ├── example.env └── run-examples.js ├── package-lock.json ├── package.json ├── postinstall.js ├── src ├── Account.js ├── App.js ├── CallsResource.js ├── ConsoleLogger.js ├── Conversion.js ├── Credentials.js ├── DtmfResource.js ├── FilesResource.js ├── HashGenerator.js ├── HttpClient.js ├── JwtGenerator.js ├── Media.js ├── Message.js ├── Nexmo.js ├── NullLogger.js ├── Number.js ├── NumberInsight.js ├── Pricing.js ├── Redact.js ├── ShortCode.js ├── StreamResource.js ├── TalkResource.js ├── Utils.js ├── Verify.js └── Voice.js ├── test ├── Account-test.js ├── App-test.js ├── CallResource-test.js ├── ConsoleLogger-test.js ├── Conversion-test.js ├── Credentials-test.js ├── DtmfResource-test.js ├── FilesResource-test.js ├── HashGenerator-test.js ├── HttpClient-test.js ├── JwtGenerator-test.js ├── Media-test.js ├── Message-test.js ├── Nexmo-test.js ├── NexmoChai.js ├── NexmoTestUtils.js ├── Number-pricing-test.js ├── Number-test.js ├── NumberInsight-test.js ├── Pricing-test.js ├── Redact-test.js ├── ResourceTestHelper.js ├── ShortCode-test.js ├── StreamResource-test.js ├── TalkResource-test.js ├── Utils-test.js ├── Verify-test.js ├── Voice-test.js ├── private-test.key └── public-test.key └── typings └── index.d.ts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", 4 | { 5 | "targets": { 6 | "esmodules": true, 7 | "node": "8" 8 | } 9 | }] 10 | ], 11 | "plugins": [ 12 | "add-module-exports", 13 | "@babel/plugin-proposal-object-rest-spread" 14 | ], 15 | "env": { 16 | "test": { 17 | "plugins": ["istanbul"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.js] 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | max_line_length = 80 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 5, 4 | "impliedStrict": true, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "impliedStrict": true, 8 | "experimentalObjectRestSpread": true 9 | } 10 | }, 11 | "plugins": [ 12 | "prettier" 13 | ], 14 | "rules": { 15 | "prettier/prettier": "error" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior for line endings 2 | * text=auto 3 | * text eol=lf 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | > :exclamation: The SDK and NPM package have moved! The NPM package is now is now [`@vonage/server-sdk`](https://www.npmjs.com/package/@vonage/server-sdk). The code repository is located at [vonage/vonage-node-sdk](https://github.com/vonage/vonage-node-sdk). 2 | > 3 | >We will support this repository for 12 months, ending October 2021, with any needed bug or security fixes for the last release of v2.9.1. 4 | > 5 | >New features will be released under `@vonage/server-sdk`, so to take advantage of those please make sure to switch to `@vonage/server-sdk` as soon as possible. -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release published 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | add-changelog: 7 | name: Add Changelog 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Add Changelog 11 | uses: nexmo/github-actions/nexmo-changelog@master 12 | env: 13 | CHANGELOG_AUTH_TOKEN: ${{ secrets.CHANGELOG_AUTH_TOKEN }} 14 | CHANGELOG_CATEGORY: Server SDK 15 | CHANGELOG_RELEASE_TITLE: nexmo-node 16 | CHANGELOG_SUBCATEGORY: node 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | test/.env 4 | examples/.env 5 | examples/private.key 6 | npm-debug.log 7 | .nyc 8 | coverage 9 | .nyc_output 10 | .idea 11 | .DS_Store 12 | .env 13 | .nexmo-app 14 | coverage.lcov 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | examples 3 | coverage 4 | node_modules 5 | test 6 | e2e 7 | .nyc_output 8 | coverage.lcov 9 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "check-coverage": true, 3 | "lines": 0, 4 | "statements": 0, 5 | "functions": 0, 6 | "branches": 0, 7 | "reporter": [ 8 | "html", 9 | "text" 10 | ], 11 | "require": [ 12 | "@babel/register" 13 | ], 14 | "sourceMap": false, 15 | "instrument": false 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - windows 3 | - linux 4 | - osx 5 | language: node_js 6 | node_js: 7 | - 8 8 | - 10 9 | - 11 10 | - 12 11 | install: 12 | - npm install -g codecov 13 | - npm install 14 | script: 15 | - npm run report-coverage 16 | - codecov 17 | - npm run compile 18 | - node -e "const n = require('.'); new n({});" 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## 2.9.1 6 | 7 | - FIXED: #317 - TypeError: Nexmo is not a constructor 8 | 9 | ## 2.9.0 10 | 11 | - FIXED: #295 - Nexmo constructor changes of given options object 12 | - ADDED: Optional `target_api_key` parameter for the `number.buy()` and `number.cancel()` methods. 13 | - ADDED: Typings for Messages API 14 | - UPDATED: Private Key strings now replaces `\n` with newlines for easier usage in environment variables. 15 | 16 | ## 2.8.0 17 | 18 | - ADDED: Support for Verify PSD2 requests via `nexmo.verify.psd2()`. 19 | 20 | ## 2.7.0 21 | 22 | - ADDED: Made `apiKey` and `apiSecret` optional when `applicationId` and `privateKey` are present in Nexmo constructor. 23 | 24 | ## 2.6.0 25 | 26 | - ADDED: Change host via the config object, using `apiHost` & `restHost` 27 | 28 | ## 2.5.3 29 | 30 | - FIXED: URI Encode Signed SMS Message 31 | 32 | ## 2.5.2 33 | 34 | - ADDED: Pricing API support 35 | 36 | ## 2.5.1 37 | 38 | - ADDED: typings for Verify API 39 | - ADDED: Applications API V2 support 40 | 41 | ## 2.4.2 42 | 43 | - Added message signing for for sending SMS 44 | - Added `Nexmo.generateSignature` to verify signed messages 45 | 46 | ## 2.0.1 47 | 48 | - FIXED: #116 - default setting of `retry-after` for 429 http status code responses 49 | 50 | ## 2.0.0 51 | 52 | - FIXED: #110 - check the `statusCode` on the response 53 | - FIXED: #114 - handle 429 HTTP status codes 54 | - UPDATED: To allow errors to be programmatically useful the `error` callback objects has been updated to `{statusCode: STATUS_CODE, body: JSON_BODY, headers: HEADERS}` 55 | 56 | ## 1.2.0 57 | 58 | - ADDED: Add File API to library. `nexmo.files.get` and `nexmo.files.save`. 59 | 60 | ## 1.1.2 61 | 62 | - Fixed: Bug #104 - Fix JSON parsing error 63 | 64 | ## 1.1.1 65 | 66 | - UPDATED: Changed User Agent format to match other libraries 67 | - FIXED: Bug #88 - Undefined method when missing `method` declaration 68 | 69 | ## 1.1.0 70 | 71 | - ADDED: `nexmo.generateJwt` to generate JWT based on instance credentials 72 | - ADDED: `Nexmo.generateJwt` static function to generate JWT 73 | 74 | ## [1.0.0] 75 | 76 | - ADDED: `applicationId` and `privateKey` properties to first constructor parameter to support JWT generation. 77 | - ADDED: `options.logger` to constructor 2nd parameter to allow adding customer logger. 78 | - ADDED: `options.appendToUserAgent` to constructor 2nd paramater to append custom string to `User-Agent` header sent to Nexmo. 79 | - ADDED: nexmo.calls adding support to `create`, `get`, `update` and `delete` calls. 80 | - ADDED: nexmo.applications adding support to `create`, `get`, `update` and `delete` calls. 81 | - ADDED: Functionality is now namespaced: 82 | - `nexmo.message` 83 | - `nexmo.calls` 84 | - `nexmo.number` 85 | - `nexmo.verify` 86 | - `nexmo.numberInsight` 87 | - `nexmo.account` 88 | - `nexmo.voice` - legacy voice functionality 89 | - CHANGED: `var Nexmo = require('nexmo');` returns a class definition which should be created using the `new` operator e.g. `var nexmo = new Nexmo(args...);`. 90 | - REMOVED: `var nexmo = require('nexmo');` no longer exposes singleton functions offered by "easynexmo". 91 | 92 | ## Pre 1.0.0 93 | 94 | Earlier versions of this library were published as "easynexmo". The "easynexmo" package is now deprecated. 95 | 96 | [1.0.0]: https://github.com/Nexmo/nexmo-node/tree/v1.0.0 97 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 7 | 8 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 9 | 10 | ## Our Standards 11 | 12 | Examples of behavior that contributes to a positive environment for our community include: 13 | 14 | * Demonstrating empathy and kindness toward other people 15 | * Being respectful of differing opinions, viewpoints, and experiences 16 | * Giving and gracefully accepting constructive feedback 17 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 18 | * Focusing on what is best not just for us as individuals, but for the overall community 19 | 20 | Examples of unacceptable behavior include: 21 | 22 | * The use of sexualized language or imagery, and sexual attention or 23 | advances of any kind 24 | * Trolling, insulting or derogatory comments, and personal or political attacks 25 | * Public or private harassment 26 | * Publishing others' private information, such as a physical or email 27 | address, without their explicit permission 28 | * Other conduct which could reasonably be considered inappropriate in a 29 | professional setting 30 | 31 | ## Enforcement Responsibilities 32 | 33 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 34 | 35 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 36 | 37 | ## Scope 38 | 39 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 40 | 41 | ## Enforcement 42 | 43 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [devrel@nexmo.com](mailto:devrel@nexmo.com). All complaints will be reviewed and investigated promptly and fairly. 44 | 45 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 46 | 47 | ## Enforcement Guidelines 48 | 49 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 50 | 51 | ### 1. Correction 52 | 53 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 54 | 55 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 56 | 57 | ### 2. Warning 58 | 59 | **Community Impact**: A violation through a single incident or series of actions. 60 | 61 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 62 | 63 | ### 3. Temporary Ban 64 | 65 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 66 | 67 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 68 | 69 | ### 4. Permanent Ban 70 | 71 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 72 | 73 | **Consequence**: A permanent ban from any sort of public interaction within the community. 74 | 75 | ## Attribution 76 | 77 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 78 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 79 | 80 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 81 | 82 | [homepage]: https://www.contributor-covenant.org 83 | 84 | For answers to common questions about this code of conduct, see the FAQ at 85 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 86 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Getting Involved 2 | 3 | Thanks for your interest in the project, we'd love to have you involved! Check out the sections below to find out more about what to do next... 4 | 5 | ## Opening an Issue 6 | 7 | We always welcome issues, if you've seen something that isn't quite right or you have a suggestion for a new feature, please go ahead and open an issue in this project. Include as much information as you have, it really helps. 8 | 9 | ## Making a Code Change 10 | 11 | We're always open to pull requests, but these should be small and clearly described so that we can understand what you're trying to do. Feel free to open an issue first and get some discussion going. 12 | 13 | When you're ready to start coding, fork this repository to your own GitHub account and make your changes in a new branch. Once you're happy, open a pull request and explain what the change is and why you think we should include it in our project. 14 | 15 | ## Reviewing a Pull Request 16 | 17 | To run the code for an open PR, follow these steps: 18 | 19 | 1. `git clone https://github.com/Nexmo/nexmo-node.git` 20 | 2. `cd nexmo-node` 21 | 3. `git checkout BRANCH_NAME` 22 | 4. `npm install` 23 | 5. `npm compile` 24 | 6. Uncomment the appropriate line in `examples/run-examples.js` for each example you want to run. 25 | 7. In the `examples` folder, copy `example.env` to `.env`. 26 | 8. Update `.env` with your API key and API secret. 27 | 9. `node examples/run-examples.js` 28 | 29 | When reviewing PRs, and switching branches to compare running examples against feature branches, don't forget to run `npm compile` after switching branches. Because examples run from the `lib/` folder, and that folder is ignored by Git, you'll need to re-compile the source every time you switch a branch. 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2018 Nexmo Inc 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the "Software"), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /MAINTENANCE.md: -------------------------------------------------------------------------------- 1 | ## A quick guide for maintainers 2 | 3 | The following is a quick cheatsheet for maintainers. 4 | 5 | * New commits are pushed to their own feature branch and merged into `master`. 6 | * The `master` branch is to be kept up to date with the latest release on [NPMjs.com](https://www.npmjs.com/). 7 | * When rolling a new release, tag this new release: 8 | * `git checkout master` 9 | * `git pull master` to make sure you have the merged content 10 | * `git tag -v v0.0.0` where `0.0.0` is your release 11 | * `git push origin v0.0.0` to push your tag to GitHub 12 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # How to use the examples 2 | 3 | The examples are here for two reasons: 4 | 5 | 1. To help with testing against the live API - maybe full integration tests will be created at some point 6 | 2. To demonstrate how to use the API 7 | 8 | Some examples are convoluted because they require a web server to be running and that web server to have a public IP address. This adds complexity. It also adds the requirement of using [ngrok](https://ngrok.com/). 9 | 10 | Looking for more focused examples? Head to the [Nexmo Node Quickstarts rep][quickstarts] 11 | 12 | That said, the naming of the files should make it clear what each example will demonstrate. 13 | 14 | ## Usage 15 | 16 | Copy `example.env` and rename to `.env` and add the appropriate values. 17 | 18 | Take a look at `run-examples.js`. This file can be run using: 19 | 20 | ```bash 21 | node run-examples.js 22 | ``` 23 | 24 | ### All examples 25 | 26 | If you want to run all the `exampleFiles` override is commented out and that `runNextExample()` is called. This will load and run all modules with an `ex-` prefix. 27 | 28 | ### Selectively running examples 29 | 30 | To selectively run examples you should either run a single example, commenting out all other `run...` lines: 31 | 32 | ```js 33 | runExample('ex-send-sms.js', console.log); 34 | ``` 35 | 36 | Update the `exampleFiles` array and make sure `runNextExample` is not commented out e.g. 37 | 38 | ```js 39 | exampleFiles = [ 40 | // 'ex-check-balance.js', 41 | // 'ex-create-update-delete-app.js', 42 | // 'ex-dtmf-to-call.js', 43 | // 'ex-get-apps.js', 44 | 'ex-get-calls.js', 45 | 'ex-make-call.js' 46 | // 'ex-number-insight-basic.js', 47 | // 'ex-send-sms.js', 48 | // 'ex-stream-to-call.js', 49 | // 'ex-talk-to-call.js' 50 | ]; 51 | 52 | runNextExample(); 53 | ``` 54 | 55 | ## Missing an example 56 | 57 | [Raise an issue](../../../issues) 58 | 59 | ## More examples 60 | 61 | You can find examples that are much more focused of achieving a single goal in the [Nexmo Node Quickstarts rep][quickstarts]. 62 | 63 | [quickstarts]: https://github.com/nexmo-community/nexmo-node-quickstart 64 | -------------------------------------------------------------------------------- /examples/config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config({ 2 | path: __dirname + '/.env' 3 | }); 4 | 5 | var config = { 6 | API_KEY: process.env.API_KEY || '', 7 | API_SECRET: process.env.API_SECRET || '', 8 | SIGNATURE_SECRET: process.env.SIGNATURE_SECRET || '', 9 | NEW_API_SECRET: process.env.NEW_API_SECRET || '', 10 | API_SECRET_ID: process.env.API_SECRET_ID || '', 11 | FROM_NUMBER: process.env.FROM_NUMBER || '', 12 | ALT_TO_NUMBER: process.env.ALT_TO_NUMBER || '', 13 | TO_NUMBER: process.env.TO_NUMBER || '', 14 | NEXMO_COUNTRY_CODE: process.env.NEXMO_COUNTRY_CODE || '', 15 | NEXMO_NUMBER: process.env.NEXMO_NUMBER || '', 16 | TO_US_NUMBER: process.env.TO_US_NUMBER || '', 17 | MEDIA_ID: process.env.MEDIA_ID || '', 18 | BRAND_NAME: process.env.BRAND_NAME || '', 19 | REQUEST_ID: process.env.REQUEST_ID || '', 20 | WORKFLOW_ID: process.env.WORKFLOW_ID || '', 21 | CODE: process.env.CODE || '', 22 | APP_ID: process.env.APP_ID || '', 23 | PRIVATE_KEY: process.env.PRIVATE_KEY || '', 24 | DEBUG: process.env.DEBUG === 'true', 25 | REST_HOST: process.env.REST_HOST || 'rest.nexmo.com', 26 | API_HOST: process.env.API_HOST || 'api.nexmo.com' 27 | }; 28 | 29 | module.exports = config; 30 | -------------------------------------------------------------------------------- /examples/ex-change-api-host.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG, 10 | apiHost: config.API_HOST 11 | }); 12 | 13 | nexmo.applications.create({ 14 | name: 'My nexmo-node Example V2 App', 15 | capabilities: { 16 | voice: { 17 | webhooks: { 18 | answer_url: { 19 | address: "https://example.com", 20 | http_method: "GET" 21 | }, 22 | event_url: { 23 | address: "https://example.com", 24 | http_method: "POST" 25 | } 26 | } 27 | }, 28 | messages: { 29 | webhooks: { 30 | inbound_url: { 31 | address: "https://example.com", 32 | http_method: "POST" 33 | }, 34 | status_url: { 35 | address: "https://example.com", 36 | http_method: "POST" 37 | } 38 | } 39 | }, 40 | rtc: { 41 | webhooks: { 42 | event_url: { 43 | address: "https://example.com", 44 | http_method: "POST" 45 | } 46 | } 47 | } 48 | } 49 | }, callback); 50 | }; 51 | -------------------------------------------------------------------------------- /examples/ex-change-rest-host.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG, 10 | restHost: config.REST_HOST 11 | }); 12 | 13 | nexmo.message.sendSms(config.FROM_NUMBER, config.TO_NUMBER, 'testing', callback); 14 | }; 15 | -------------------------------------------------------------------------------- /examples/ex-check-balance.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.account.checkBalance(callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-check-verification.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.verify.check({ 13 | request_id: config.REQUEST_ID, 14 | code: config.CODE 15 | }, callback); 16 | }; 17 | -------------------------------------------------------------------------------- /examples/ex-control-verification.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.verify.control({ 13 | request_id: config.REQUEST_ID, 14 | cmd: 'cancel' 15 | }, callback); 16 | }; 17 | -------------------------------------------------------------------------------- /examples/ex-create-secret.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.account.createSecret(config.API_KEY, config.NEW_API_SECRET, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-create-update-delete-app.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Promise = require('bluebird'); 4 | 5 | var Nexmo = require('../lib/Nexmo'); 6 | 7 | var nexmo = new Nexmo({ 8 | apiKey: config.API_KEY, 9 | apiSecret: config.API_SECRET 10 | }, { 11 | debug: config.DEBUG 12 | }); 13 | 14 | var app = Promise.promisifyAll(nexmo.app); 15 | 16 | var tempAppName = new Date().getTime(); 17 | console.log('Creating App', tempAppName); 18 | 19 | app.createAsync(tempAppName, 'voice', 'https://v1uxw2scimhr.runscope.net', 'https://v1uxw2scimhr.runscope.net', null) 20 | .then(function(createResp) { 21 | console.log('Updating App', tempAppName); 22 | return app.updateAsync(createResp.id, tempAppName, 'voice', 'https://v1uxw2scimhr.runscope.net', 'https://v1uxw2scimhr.runscope.net', null); 23 | }) 24 | .then(function(updateResp) { 25 | console.log('Deleting App', tempAppName); 26 | return app.deleteAsync(updateResp.id); 27 | }) 28 | .then(function(deleteResp) { 29 | console.log('App Deleted'); 30 | callback(null, deleteResp); 31 | }) 32 | .catch(callback); 33 | }; 34 | -------------------------------------------------------------------------------- /examples/ex-create-v1-application.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | const name = 'My nexmo-node Messages App'; 13 | const type = 'messages'; 14 | const answerUrl = 'https://example.com'; // webhook that points to NCCO 15 | const eventUrl = 'https://example.com'; 16 | 17 | let options = { 18 | inbound_url: 'https://example.com', 19 | status_url: 'https://example.com' 20 | }; 21 | 22 | nexmo.applications.create(name, type, answerUrl, eventUrl, options, callback); 23 | }; 24 | -------------------------------------------------------------------------------- /examples/ex-create-v2-application.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.applications.create({ 13 | name: 'My nexmo-node Example V2 App', 14 | capabilities: { 15 | voice: { 16 | webhooks: { 17 | answer_url: { 18 | address: "https://example.com", 19 | http_method: "GET" 20 | }, 21 | event_url: { 22 | address: "https://example.com", 23 | http_method: "POST" 24 | } 25 | } 26 | }, 27 | messages: { 28 | webhooks: { 29 | inbound_url: { 30 | address: "https://example.com", 31 | http_method: "POST" 32 | }, 33 | status_url: { 34 | address: "https://example.com", 35 | http_method: "POST" 36 | } 37 | } 38 | }, 39 | rtc: { 40 | webhooks: { 41 | event_url: { 42 | address: "https://example.com", 43 | http_method: "POST" 44 | } 45 | } 46 | } 47 | } 48 | }, callback); 49 | }; 50 | -------------------------------------------------------------------------------- /examples/ex-delete-application.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, 9 | {debug: config.DEBUG} 10 | ); 11 | 12 | nexmo.applications.delete(config.APP_ID, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-dtmf-to-call.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Promise = require('bluebird'); 4 | var ngrok = require('ngrok'); 5 | var SPACER = '\n****\n\n'; 6 | 7 | var Nexmo = require('../lib/Nexmo'); 8 | var nexmo = new Nexmo({ 9 | apiKey: config.API_KEY, 10 | apiSecret: config.API_SECRET, 11 | applicationId: config.APP_ID, 12 | privateKey: config.PRIVATE_KEY 13 | }, { 14 | debug: config.DEBUG 15 | }); 16 | var calls = Promise.promisifyAll(nexmo.calls); 17 | var dtmf = Promise.promisifyAll(nexmo.calls.dtmf); 18 | 19 | function randomPort() { 20 | return Math.floor(Math.random() * (7000 - 3000 + 1) + 3000); 21 | } 22 | 23 | var app = require('express')(); 24 | app.set('port', (process.env.PORT || randomPort())); 25 | app.use(require('body-parser').json()); 26 | 27 | // Handle events 28 | var callId = null; 29 | app.post('/', function(req, res) { 30 | console.log('Request received', req.body); 31 | // Hack: seeing two inbound webhook requests. 32 | // Use callId to indicate this. 33 | if (req.body.status === 'answered' && !callId) { 34 | callId = req.body.uuid; 35 | 36 | console.log('Call answered with call_uuid', callId) 37 | 38 | setTimeout(function() { 39 | sendDtmf(callId); 40 | }, 5000); 41 | } 42 | 43 | res.sendStatus(204); 44 | }); 45 | 46 | var server = app.listen(app.get('port'), makeCall); 47 | 48 | function makeCall() { 49 | console.log('Web server listening on port', app.get('port')); 50 | 51 | Promise.promisify(ngrok.connect)(app.get('port')) 52 | .then(function(url) { 53 | console.log('ngrok tunnel set up:', url); 54 | 55 | console.log('calling', config.TO_NUMBER); 56 | return calls.createAsync({ 57 | to: [{ 58 | type: 'phone', 59 | number: config.TO_NUMBER 60 | }], 61 | from: { 62 | type: 'phone', 63 | number: config.FROM_NUMBER 64 | }, 65 | answer_url: ['https://nexmo-community.github.io/ncco-examples/conference.json'], 66 | event_url: [url] 67 | }); 68 | }) 69 | .then(function(res) { 70 | console.log('call in progress', res); 71 | }) 72 | .catch(callback); 73 | } 74 | 75 | function sendDtmf(callId) { 76 | dtmf.sendAsync(callId, { 77 | digits: '1234' 78 | }) 79 | .then(function(res) { 80 | console.log('dtmf.send res', res); 81 | 82 | return calls.updateAsync(callId, { 83 | action: 'hangup' 84 | }); 85 | }) 86 | .then(function(res) { 87 | console.log('calls.update', res); 88 | 89 | server.close(); 90 | ngrok.kill(); 91 | 92 | return Promise.delay(2000); 93 | }) 94 | .then(function() { 95 | callback(null, null); 96 | }) 97 | .catch(function(err) { 98 | if (server) server.close(); 99 | if (ngrok) ngrok.kill(); 100 | 101 | callback(err); 102 | }); 103 | } 104 | 105 | }; 106 | -------------------------------------------------------------------------------- /examples/ex-get-calls.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Promise = require('bluebird'); 4 | var SPACER = '\n****\n\n'; 5 | 6 | var Nexmo = require('../lib/Nexmo'); 7 | 8 | var nexmo = new Nexmo({ 9 | apiKey: config.API_KEY, 10 | apiSecret: config.API_SECRET, 11 | applicationId: config.APP_ID, 12 | privateKey: config.PRIVATE_KEY 13 | }, { 14 | debug: config.DEBUG 15 | }); 16 | 17 | var calls = Promise.promisifyAll(nexmo.calls); 18 | var callId = null; 19 | nexmo.calls.getAsync({}) 20 | .then(function(resp) { 21 | console.log(resp._embedded.calls); 22 | 23 | callId = resp._embedded.calls[0].uuid; 24 | console.log(SPACER, 'Getting single call details', callId); 25 | return calls.getAsync(callId); 26 | }) 27 | .then(function(resp) { 28 | console.log('single call details', resp); 29 | 30 | console.log(SPACER, 'Getting calls by query'); 31 | return calls.getAsync({ 32 | status: 'completed' 33 | }); 34 | }) 35 | .then(function(resp) { 36 | console.log('Call query response', resp); 37 | 38 | console.log(SPACER, 'Updating a call', callId); 39 | return calls.updateAsync(callId, { 40 | action: 'hangup' 41 | }); 42 | }) 43 | .then(function(resp) { 44 | callback(null, resp); 45 | }) 46 | .catch(callback); 47 | }; 48 | -------------------------------------------------------------------------------- /examples/ex-get-full-pricing.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.pricing.getFull("sms", callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-get-phone-pricing-number.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.number.getPhonePricing("sms", "442038659460", callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-get-phone-pricing.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.pricing.getPhone("sms", "442038659460", callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-get-prefix-pricing.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.pricing.getPrefix("sms", "44", callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-get-pricing-number.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.number.getPricing("sms", "GB", callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-get-pricing.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.pricing.get("sms", "GB", callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-get-secret.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.account.getSecret(config.API_KEY, config.API_SECRET_ID, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-get-v1-application.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, 9 | {debug: config.DEBUG} 10 | ); 11 | 12 | nexmo.applications.get(config.APP_ID, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-get-v1-applications.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, 9 | {debug: config.DEBUG} 10 | ); 11 | 12 | nexmo.applications.get({page_size: 5}, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-get-v2-application.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, 9 | {debug: config.DEBUG} 10 | ); 11 | 12 | nexmo.applications.get(config.APP_ID, callback, true); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-get-v2-applications.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, 9 | {debug: config.DEBUG} 10 | ); 11 | 12 | nexmo.applications.get({page_size: 5}, callback, true); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-keyfile-creds.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | var Nexmo = require("../lib/Nexmo"); 3 | 4 | var nexmo = new Nexmo( 5 | { 6 | applicationId: config.APP_ID, 7 | privateKey: config.PRIVATE_KEY 8 | }, 9 | { debug: config.DEBUG } 10 | ); 11 | 12 | if(!nexmo.credentials.privateKey) { 13 | // no key found 14 | throw new Error("PrivateKey not loaded"); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /examples/ex-list-secrets.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.account.listSecrets(config.API_KEY, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-make-call-ncco.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET, 8 | applicationId: config.APP_ID, 9 | privateKey: config.PRIVATE_KEY 10 | }, { 11 | debug: config.DEBUG 12 | }); 13 | 14 | nexmo.calls.create({ 15 | to: [{ 16 | type: 'phone', 17 | number: config.TO_NUMBER 18 | }], 19 | from: { 20 | type: 'phone', 21 | number: config.FROM_NUMBER 22 | }, 23 | ncco: [{ 24 | "action": "talk", 25 | "text": "This is a text to speech call from Nexmo" 26 | }] 27 | }, callback); 28 | }; 29 | -------------------------------------------------------------------------------- /examples/ex-make-call.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET, 8 | applicationId: config.APP_ID, 9 | privateKey: config.PRIVATE_KEY 10 | }, { 11 | debug: config.DEBUG 12 | }); 13 | 14 | nexmo.calls.create({ 15 | to: [{ 16 | type: 'phone', 17 | number: config.TO_NUMBER 18 | }], 19 | from: { 20 | type: 'phone', 21 | number: config.FROM_NUMBER 22 | }, 23 | answer_url: ['https://nexmo-community.github.io/ncco-examples/first_call_talk.json'] 24 | }, callback); 25 | }; 26 | -------------------------------------------------------------------------------- /examples/ex-make-multiple-calls.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET, 8 | applicationId: config.APP_ID, 9 | privateKey: config.PRIVATE_KEY 10 | }, { 11 | debug: config.DEBUG 12 | }); 13 | 14 | function callWithRetry(callRequest, callback, maxRetries, retryCount) { 15 | retryCount = retryCount || 0; 16 | 17 | nexmo.calls.create(callRequest, function(error, result) { 18 | console.log('----------------------------'); 19 | console.log('call response returned'); 20 | console.log('error', JSON.stringify(error, null, 2)); 21 | console.log('result', JSON.stringify(result, null, 2)); 22 | 23 | if (error && error.statusCode === 429 && retryCount <= maxRetries) { 24 | console.log('429 detected. Retrying after', error.headers['retry-after'], 'ms'); 25 | setTimeout(function() { 26 | callWithRetry(callRequest, callback, maxRetries, ++retryCount); 27 | }, error.headers['retry-after']); 28 | 29 | } else { 30 | callback(error, result); 31 | } 32 | }); 33 | } 34 | 35 | const callsToMake = [{ 36 | to: [{ 37 | type: 'phone', 38 | number: config.TO_NUMBER 39 | }], 40 | from: { 41 | type: 'phone', 42 | number: config.FROM_NUMBER 43 | }, 44 | answer_url: ['https://nexmo-community.github.io/ncco-examples/first_call_talk.json'], 45 | event_url: ['http://requestb.in/wgapg3wg?to=' + config.TO_NUMBER] 46 | }, 47 | { 48 | to: [{ 49 | type: 'phone', 50 | number: config.ALT_TO_NUMBER 51 | }], 52 | from: { 53 | type: 'phone', 54 | number: config.FROM_NUMBER 55 | }, 56 | answer_url: ['https://nexmo-community.github.io/ncco-examples/first_call_talk.json'], 57 | event_url: ['http://requestb.in/wgapg3wg?to=' + config.ALT_TO_NUMBER] 58 | } 59 | ]; 60 | 61 | const results = []; 62 | 63 | function makeNextCall(error, result) { 64 | if (error || result) { 65 | results.push({ 66 | error: error, 67 | result: result 68 | }); 69 | } 70 | 71 | if (callsToMake.length > 0) { 72 | var callRequest = callsToMake.shift(); 73 | callWithRetry(callRequest, makeNextCall, 1); 74 | } else { 75 | console.log('--------------------------'); 76 | console.log('all calls completed', results); 77 | console.log('--------------------------'); 78 | callback(null, results); 79 | } 80 | } 81 | 82 | makeNextCall(); 83 | 84 | }; 85 | -------------------------------------------------------------------------------- /examples/ex-media-delete.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET, 8 | applicationId: config.APP_ID, 9 | privateKey: config.PRIVATE_KEY 10 | }, { 11 | debug: config.DEBUG 12 | }); 13 | 14 | nexmo.media.delete(config.MEDIA_ID, function(err, data) { 15 | if (err) { 16 | throw err; 17 | } 18 | console.log(data); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /examples/ex-media-download.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET, 8 | applicationId: config.APP_ID, 9 | privateKey: config.PRIVATE_KEY 10 | }, { 11 | debug: config.DEBUG 12 | }); 13 | 14 | nexmo.media.download(config.MEDIA_ID, function(err, data) { 15 | if (err) { 16 | throw err; 17 | } 18 | console.log(data.toString()); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /examples/ex-media-get.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET, 8 | applicationId: config.APP_ID, 9 | privateKey: config.PRIVATE_KEY 10 | }, { 11 | debug: config.DEBUG 12 | }); 13 | 14 | nexmo.media.get(config.MEDIA_ID, function(err, data) { 15 | if (err) { 16 | throw err; 17 | } 18 | console.log(data); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /examples/ex-media-search.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET, 8 | applicationId: config.APP_ID, 9 | privateKey: config.PRIVATE_KEY 10 | }, { 11 | debug: config.DEBUG 12 | }); 13 | 14 | nexmo.media.search({ 15 | page_size: 1, 16 | page_index: 1 17 | }, function(err, data) { 18 | if (err) { 19 | throw err; 20 | } 21 | console.log(data); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /examples/ex-media-update.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET, 8 | applicationId: config.APP_ID, 9 | privateKey: config.PRIVATE_KEY 10 | }, { 11 | debug: config.DEBUG 12 | }); 13 | 14 | nexmo.media.update(config.MEDIA_ID, { 15 | public_item: true 16 | }, function(err, data) { 17 | if (err) { 18 | throw err; 19 | } 20 | console.log(data); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /examples/ex-media-upload.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET, 8 | applicationId: config.APP_ID, 9 | privateKey: config.PRIVATE_KEY 10 | }, { 11 | debug: config.DEBUG 12 | }); 13 | 14 | nexmo.media.upload({ 15 | //file: "/path/to/file", // If you want to upload a file instead of fetching a URL, set this instead 16 | url: "https://www.nexmo.com/wp-content/uploads/2016/07/nexmo_vonage_color.jpg", 17 | info: { 18 | test: "content" 19 | } 20 | }, 21 | function(err, data) { 22 | if (err) { 23 | throw err; 24 | } 25 | console.log(data); 26 | } 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /examples/ex-number-buy.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.number.buy(config.NEXMO_COUNTRY_CODE, config.NEXMO_NUMBER, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-number-cancel.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.number.cancel(config.NEXMO_COUNTRY_CODE, config.NEXMO_NUMBER, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-number-get.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.number.get({}, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-number-insight-advanced-async.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.numberInsight.get({ 13 | level: 'advanced', 14 | number: config.TO_NUMBER, 15 | callback: "http://example.com" 16 | }, callback); 17 | }; 18 | -------------------------------------------------------------------------------- /examples/ex-number-insight-advanced-sync.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.numberInsight.get({ 13 | level: 'advancedSync', 14 | number: config.TO_NUMBER 15 | }, callback); 16 | }; 17 | -------------------------------------------------------------------------------- /examples/ex-number-insight-basic.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.numberInsight.get({ 13 | level: 'basic', 14 | number: config.TO_NUMBER 15 | }, callback); 16 | }; 17 | -------------------------------------------------------------------------------- /examples/ex-number-insight-standard.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.numberInsight.get({ 13 | level: 'standard', 14 | number: config.TO_NUMBER 15 | }, callback); 16 | }; 17 | -------------------------------------------------------------------------------- /examples/ex-number-pricing.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | // You can get general pricing for a country 13 | nexmo.number.getPricing('GB', console.log); 14 | 15 | // Or you can find out the price to call/sms a specific number 16 | nexmo.number.getPhonePricing('voice', '447700900000', console.log) 17 | }; 18 | -------------------------------------------------------------------------------- /examples/ex-number-search.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.number.search("GB", callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-number-update.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.number.update("GB", config.FROM_NUMBER, {app_id: config.APP_ID}, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-redact-transaction.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.redact.transaction(config.REDACT_ID, config.REDACT_TYPE, function(err, data) { 13 | if (err) { 14 | throw err; 15 | } 16 | // Returns a 204 if successful, so no response body 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /examples/ex-revoke-secret.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.account.deleteSecret(config.API_KEY, config.API_SECRET_ID, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-search-verification.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.verify.search(config.REQUEST_ID, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-send-binary-sms.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | // Sends an SMS with the text WIN 13 | nexmo.message.sendBinaryMessage(config.FROM_NUMBER, config.TO_NUMBER, '57494E', '050003CC0101', { 14 | "protocol-id": "65" 15 | }, callback); 16 | }; 17 | -------------------------------------------------------------------------------- /examples/ex-send-psd2-verification-with-workflow.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.verify.psd2({ 13 | number: config.TO_NUMBER, 14 | payee: config.BRAND_NAME, 15 | amount: "10", 16 | workflow_id: config.WORKFLOW_ID 17 | }, callback); 18 | }; 19 | -------------------------------------------------------------------------------- /examples/ex-send-psd2-verification.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.verify.psd2({ 13 | number: config.TO_NUMBER, 14 | payee: config.BRAND_NAME, 15 | amount: "10", 16 | }, callback); 17 | }; 18 | -------------------------------------------------------------------------------- /examples/ex-send-shortcode-2fa.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.message.shortcode2FA(config.TO_US_NUMBER, {"company-name": "Acme", "pin": "1234"}, {}, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-send-shortcode-alert.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.message.shortcodeAlert(config.TO_US_NUMBER, {time: "now", place: "here"}, {}, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-send-signed-sms.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET, 8 | signatureSecret: config.SIGNATURE_SECRET, 9 | signatureMethod: "md5hash" 10 | }, { 11 | debug: config.DEBUG 12 | }); 13 | 14 | nexmo.message.sendSms(config.FROM_NUMBER, config.TO_NUMBER, 'testing 123', callback); 15 | }; 16 | -------------------------------------------------------------------------------- /examples/ex-send-sms.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.message.sendSms(config.FROM_NUMBER, config.TO_NUMBER, 'testing', callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-send-tts.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.voice.sendTTSMessage(config.TO_NUMBER, 'testing', {}, callback); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/ex-send-verification-with-workflow.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.verify.request({ 13 | number: config.TO_NUMBER, 14 | brand: config.BRAND_NAME, 15 | workflow_id: config.WORKFLOW_ID 16 | }, callback); 17 | }; 18 | -------------------------------------------------------------------------------- /examples/ex-send-verification.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.verify.request({ 13 | number: config.TO_NUMBER, 14 | brand: config.BRAND_NAME 15 | }, callback); 16 | }; 17 | -------------------------------------------------------------------------------- /examples/ex-send-wap-message.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | // Unless you have a phone capable of receiving WAP messages, you won't be able to test this. 13 | nexmo.message.sendWapPushMessage(config.FROM_NUMBER, config.TO_NUMBER, 'Test', 'http://example.com', callback); 14 | }; 15 | -------------------------------------------------------------------------------- /examples/ex-stream-to-call.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Promise = require('bluebird'); 4 | var ngrok = require('ngrok'); 5 | var SPACER = '\n****\n\n'; 6 | 7 | var Nexmo = require('../lib/Nexmo'); 8 | var nexmo = new Nexmo({ 9 | apiKey: config.API_KEY, 10 | apiSecret: config.API_SECRET, 11 | applicationId: config.APP_ID, 12 | privateKey: config.PRIVATE_KEY 13 | }, { 14 | debug: config.DEBUG 15 | }); 16 | var calls = Promise.promisifyAll(nexmo.calls); 17 | var stream = Promise.promisifyAll(nexmo.calls.stream); 18 | 19 | function randomPort() { 20 | return Math.floor(Math.random() * (7000 - 3000 + 1) + 3000); 21 | } 22 | 23 | var app = require('express')(); 24 | app.set('port', (process.env.PORT || randomPort())); 25 | app.use(require('body-parser').json()); 26 | 27 | app.get('/', function(req, res) { 28 | res.send('hello'); 29 | }); 30 | 31 | // Handle events 32 | var callId = null; 33 | app.post('/', function(req, res) { 34 | console.log('Request received', req.body); 35 | // Hack: seeing two inbound webhook requests. 36 | // Use callId to indicate this. 37 | if (req.body.status === 'answered' && !callId) { 38 | callId = req.body.uuid; 39 | 40 | console.log('Call answered with call_uuid', callId) 41 | 42 | setTimeout(function() { 43 | sendStream(callId); 44 | }, 5000); 45 | } 46 | 47 | res.sendStatus(204); 48 | }); 49 | 50 | var server = app.listen(app.get('port'), makeCall); 51 | 52 | function makeCall() { 53 | console.log('Web server listening on port', app.get('port')); 54 | 55 | Promise.promisify(ngrok.connect)(app.get('port')) 56 | .then(function(url) { 57 | console.log('ngrok tunnel set up:', url); 58 | 59 | console.log('calling', config.TO_NUMBER); 60 | return calls.createAsync({ 61 | to: [{ 62 | type: 'phone', 63 | number: config.TO_NUMBER 64 | }], 65 | from: { 66 | type: 'phone', 67 | number: config.FROM_NUMBER 68 | }, 69 | answer_url: ['https://nexmo-community.github.io/ncco-examples/conference.json'], 70 | event_url: [url] 71 | }); 72 | }) 73 | .then(function(res) { 74 | console.log('call in progress', res); 75 | }) 76 | .catch(callback); 77 | } 78 | 79 | function sendStream(callId) { 80 | 81 | stream.startAsync( 82 | callId, { 83 | stream_url: [ 84 | 'https://nexmo-community.github.io/ncco-examples/assets/voice_api_audio_streaming.mp3' 85 | ], 86 | loop: 1 87 | }) 88 | .then(function(resp) { 89 | console.log('stream.start response', resp); 90 | 91 | console.log('waiting a short time'); 92 | return Promise.delay(2000); 93 | }) 94 | .then(function(resp) { 95 | console.log('stopping the stream', resp); 96 | 97 | return nexmo.calls.stream.stopAsync(callId); 98 | }) 99 | .then(function(res) { 100 | console.log('stream.stop res', res); 101 | 102 | console.log('waiting a short time'); 103 | return Promise.delay(2000); 104 | }) 105 | .then(function(res) { 106 | return calls.updateAsync(callId, { 107 | action: 'hangup' 108 | }); 109 | }) 110 | .then(function(res) { 111 | console.log('calls.update', res); 112 | 113 | server.close(); 114 | ngrok.kill(); 115 | 116 | return Promise.delay(2000); 117 | }) 118 | .then(function() { 119 | callback(null, null); 120 | }) 121 | .catch(function(err) { 122 | if (server) server.close(); 123 | if (ngrok) ngrok.kill(); 124 | 125 | callback(err); 126 | }); 127 | } 128 | 129 | }; 130 | -------------------------------------------------------------------------------- /examples/ex-talk-to-call.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Promise = require('bluebird'); 4 | var ngrok = require('ngrok'); 5 | var SPACER = '\n****\n\n'; 6 | 7 | var Nexmo = require('../lib/Nexmo'); 8 | var nexmo = new Nexmo({ 9 | apiKey: config.API_KEY, 10 | apiSecret: config.API_SECRET, 11 | applicationId: config.APP_ID, 12 | privateKey: config.PRIVATE_KEY 13 | }, { 14 | debug: config.DEBUG 15 | }); 16 | var calls = Promise.promisifyAll(nexmo.calls); 17 | var talk = Promise.promisifyAll(nexmo.calls.talk); 18 | 19 | function randomPort() { 20 | return Math.floor(Math.random() * (7000 - 3000 + 1) + 3000); 21 | } 22 | 23 | var app = require('express')(); 24 | app.set('port', (process.env.PORT || randomPort())); 25 | app.use(require('body-parser').json()); 26 | 27 | app.get('/', function(req, res) { 28 | res.send('hello'); 29 | }); 30 | 31 | // Handle events 32 | var callId = null; 33 | app.post('/', function(req, res) { 34 | console.log('Request received', req.body); 35 | // Hack: seeing two inbound webhook requests. 36 | // Use callId to indicate this. 37 | if (req.body.status === 'answered' && !callId) { 38 | callId = req.body.uuid; 39 | 40 | console.log('Call answered with call_uuid', callId) 41 | 42 | setTimeout(function() { 43 | sendTalk(callId); 44 | }, 5000); 45 | } 46 | 47 | res.sendStatus(204); 48 | }); 49 | 50 | var server = app.listen(app.get('port'), makeCall); 51 | 52 | function makeCall() { 53 | console.log('Web server listening on port', app.get('port')); 54 | 55 | Promise.promisify(ngrok.connect)(app.get('port')) 56 | .then(function(url) { 57 | console.log('ngrok tunnel set up:', url); 58 | 59 | console.log('calling', config.TO_NUMBER); 60 | return calls.createAsync({ 61 | to: [{ 62 | type: 'phone', 63 | number: config.TO_NUMBER 64 | }], 65 | from: { 66 | type: 'phone', 67 | number: config.FROM_NUMBER 68 | }, 69 | answer_url: ['https://nexmo-community.github.io/ncco-examples/conference.json'], 70 | event_url: [url] 71 | }); 72 | }) 73 | .then(function(res) { 74 | console.log('call in progress', res); 75 | }) 76 | .catch(callback); 77 | } 78 | 79 | function sendTalk(callId) { 80 | console.log('Sending a talk into the call') 81 | return talk.startAsync(callId, { 82 | text: "Sean... He's on the beach now, a toe in the water. He's asking you to come in with him. He's been racing his mother up and down the sand. There's so much love in this house. He's ten years old. He's surrounded by animals. He wants to be a vet. You keep a rabbit for him, a bird and a fox. He's in high school. He likes to run, like his father.", 83 | voice_name: 'Emma', 84 | loop: 0 85 | }) 86 | .then(function(resp) { 87 | console.log('talk.start response', resp); 88 | 89 | console.log('waiting a short time'); 90 | return Promise.delay(5000); 91 | }) 92 | .then(function() { 93 | console.log(SPACER, 'Stopping talking'); 94 | 95 | return talk.stopAsync(callId); 96 | }) 97 | .then(function(res) { 98 | console.log('talk.stop res', res); 99 | 100 | return calls.updateAsync(callId, { 101 | action: 'hangup' 102 | }); 103 | }) 104 | .then(function(res) { 105 | console.log('calls.update', res); 106 | 107 | server.close(); 108 | ngrok.kill(); 109 | 110 | return Promise.delay(2000); 111 | }) 112 | .then(function() { 113 | callback(null, null); 114 | }) 115 | .catch(function(err) { 116 | if (server) server.close(); 117 | if (ngrok) ngrok.kill(); 118 | 119 | callback(err); 120 | }); 121 | } 122 | 123 | }; 124 | -------------------------------------------------------------------------------- /examples/ex-update-v1-application.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | const name = 'My updated nexmo-node Messages App'; 13 | const type = 'messages'; 14 | const answerUrl = 'https://example.com'; // webhook that points to NCCO 15 | const eventUrl = 'https://example.com'; 16 | 17 | let options = { 18 | inbound_url: 'https://example.com', 19 | status_url: 'https://example.com' 20 | }; 21 | 22 | nexmo.applications.update(config.APP_ID, name, type, answerUrl, eventUrl, options, callback); 23 | }; 24 | -------------------------------------------------------------------------------- /examples/ex-update-v2-application.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET 8 | }, { 9 | debug: config.DEBUG 10 | }); 11 | 12 | nexmo.applications.update(config.APP_ID, { 13 | name: 'My updated nexmo-node Example V2 App', 14 | capabilities: { 15 | voice: { 16 | webhooks: { 17 | answer_url: { 18 | address: "https://example.com", 19 | http_method: "GET" 20 | }, 21 | event_url: { 22 | address: "https://example.com", 23 | http_method: "POST" 24 | } 25 | } 26 | }, 27 | messages: { 28 | webhooks: { 29 | inbound_url: { 30 | address: "https://example.com", 31 | http_method: "POST" 32 | }, 33 | status_url: { 34 | address: "https://example.com", 35 | http_method: "POST" 36 | } 37 | } 38 | }, 39 | rtc: { 40 | webhooks: { 41 | event_url: { 42 | address: "https://example.com", 43 | http_method: "POST" 44 | } 45 | } 46 | } 47 | } 48 | }, callback); 49 | }; 50 | -------------------------------------------------------------------------------- /examples/ex-verify-signed-sms-without-instance.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | const app = require('express')() 6 | const bodyParser = require('body-parser') 7 | 8 | app.use(bodyParser.json()) 9 | app.use(bodyParser.urlencoded({ extended: true })) 10 | 11 | app 12 | .route('/webhooks/inbound-message') 13 | .get(handleInboundSms) 14 | .post(handleInboundSms) 15 | 16 | function handleInboundSms(request, response) { 17 | const params = Object.assign(request.query, request.body) 18 | 19 | if (Nexmo.generateSignature("md5hash", config.SIGNATURE_SECRET, params) === params.sig) { 20 | console.log("Valid signature"); 21 | } else { 22 | console.log("Invalid signature" 23 | ); 24 | } 25 | console.log(params) 26 | 27 | response.status(204).send() 28 | } 29 | 30 | app.listen(3000) 31 | 32 | }; 33 | -------------------------------------------------------------------------------- /examples/ex-verify-signed-sms.js: -------------------------------------------------------------------------------- 1 | module.exports = function(callback, config) { 2 | 3 | var Nexmo = require('../lib/Nexmo'); 4 | 5 | var nexmo = new Nexmo({ 6 | apiKey: config.API_KEY, 7 | apiSecret: config.API_SECRET, 8 | signatureSecret: config.SIGNATURE_SECRET, 9 | signatureMethod: "md5hash" 10 | }, { 11 | debug: config.DEBUG 12 | }); 13 | 14 | const app = require('express')() 15 | const bodyParser = require('body-parser') 16 | 17 | app.use(bodyParser.json()) 18 | app.use(bodyParser.urlencoded({ extended: true })) 19 | 20 | app 21 | .route('/webhooks/inbound-message') 22 | .get(handleInboundSms) 23 | .post(handleInboundSms) 24 | 25 | function handleInboundSms(request, response) { 26 | const params = Object.assign(request.query, request.body) 27 | 28 | if (nexmo.generateSignature(params) === params.sig) { 29 | console.log("Valid signature"); 30 | } else { 31 | console.log("Invalid signature"); 32 | } 33 | console.log(params) 34 | 35 | response.status(204).send() 36 | } 37 | 38 | app.listen(3000) 39 | 40 | }; 41 | -------------------------------------------------------------------------------- /examples/example.env: -------------------------------------------------------------------------------- 1 | API_KEY= 2 | API_SECRET= 3 | SIGNATURE_SECRET= 4 | NEW_API_SECRET= 5 | API_SECRET_ID= 6 | FROM_NUMBER= 7 | TO_NUMBER= 8 | NEXMO_NUMBER= 9 | NEXMO_COUNTRY_CODE= 10 | TO_US_NUMBER= 11 | BRAND_NAME= 12 | REQUEST_ID= 13 | WORKFLOW_ID= 14 | CODE= 15 | APP_ID= 16 | CONVERSATION_ID= 17 | USER_ID= 18 | MEMBER_ID= 19 | EVENT_ID= 20 | SERVICE_MESSAGE_ID= 21 | PRIVATE_KEY=./examples/private.key 22 | DEBUG=true 23 | MEDIA_ID= 24 | REST_HOST= 25 | API_HOST= 26 | -------------------------------------------------------------------------------- /examples/run-examples.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run all the examples in the `examples` directory. 3 | */ 4 | 5 | var fs = require('fs'); 6 | 7 | var config = require('./config'); 8 | console.log(config); 9 | 10 | var SPACER = '\n-------------------------------------\n\n'; 11 | 12 | var exampleFiles = fs.readdirSync(__dirname); 13 | // only get the examples files identified by file naming convention 14 | exampleFiles = exampleFiles.filter(function(fileName) { 15 | return fileName.match(/^ex\-.*\.js$/); 16 | }); 17 | var exampleIndex = 0; 18 | var currentExampleFile = null; 19 | var failures = []; 20 | 21 | function runNextExample(err, res) { 22 | if (err) { 23 | console.error('Error running', currentExampleFile, 'Error', err); 24 | failures.push({ 25 | example: currentExampleFile, 26 | error: err 27 | }); 28 | } else if (currentExampleFile) { 29 | console.log('Example complete:', currentExampleFile, 'Result', res); 30 | } 31 | 32 | if (exampleIndex < exampleFiles.length) { 33 | currentExampleFile = exampleFiles[exampleIndex]; 34 | ++exampleIndex; 35 | 36 | console.log(SPACER, exampleIndex + '.', 'Loading', currentExampleFile); 37 | runExample(currentExampleFile, runNextExample); 38 | } else { 39 | console.log(SPACER, 'All examples complete'); 40 | if (failures.length > 0) { 41 | console.error(SPACER, failures.length, 'example(s) provided errors\n\n', failures); 42 | } 43 | } 44 | } 45 | 46 | function runExample(exampleFile, callback) { 47 | var example = require(__dirname + '/' + exampleFile); 48 | 49 | console.log('Starting', exampleFile); 50 | example(callback, config); 51 | } 52 | 53 | // By default all examples are run. 54 | // Use this array to run a select number of examples. 55 | exampleFiles = [ 56 | // 'ex-keyfile-creds.js', 57 | // 'ex-change-api-host.js', 58 | // 'ex-change-rest-host.js', 59 | // 'ex-check-balance.js', 60 | // 'ex-create-update-delete-app.js', 61 | // 'ex-dtmf-to-call.js', 62 | // 'ex-make-call-ncco.js', 63 | // 'ex-send-verification.js', 64 | // 'ex-send-verification-with-workflow.js', 65 | // 'ex-send-psd2-verification.js', 66 | // 'ex-send-psd2-verification-with-workflow.js', 67 | // 'ex-check-verification.js', 68 | // 'ex-control-verification.js', 69 | // 'ex-search-verification.js', 70 | // 'ex-get-apps.js', 71 | // 'ex-get-calls.js', 72 | // 'ex-make-call.js', 73 | // 'ex-number-insight-basic.js', 74 | // 'ex-number-insight-standard.js', 75 | // 'ex-number-insight-advanced-sync.js', 76 | // 'ex-number-insight-advanced-async.js', 77 | // 'ex-send-shortcode-alert.js', 78 | // 'ex-send-shortcode-2fa.js', 79 | // 'ex-send-sms.js', 80 | // 'ex-send-binary-sms.js', 81 | // 'ex-send-wap-message.js', 82 | // 'ex-send-signed-sms.js', 83 | // 'ex-verify-signed-sms.js', 84 | // 'ex-verify-signed-sms-without-instance.js', 85 | // 'ex-send-tts.js', 86 | // 'ex-stream-to-call.js', 87 | // 'ex-talk-to-call.js', 88 | // 'ex-create-secret.js', 89 | // 'ex-get-secret.js', 90 | // 'ex-list-secrets.js', 91 | // 'ex-revoke-secret.js', 92 | // 'ex-create-v1-application.js', 93 | // 'ex-create-v2-application.js', 94 | // 'ex-update-v1-application.js', 95 | // 'ex-update-v2-application.js', 96 | // 'ex-get-v1-application.js', 97 | // 'ex-get-v1-applications.js', 98 | // 'ex-get-v2-application.js', 99 | // 'ex-get-v2-applications.js', 100 | // 'ex-delete-application.js', 101 | // 'ex-get-pricing.js', 102 | // 'ex-get-full-pricing.js', 103 | // 'ex-get-prefix-pricing.js', 104 | // 'ex-get-phone-pricing.js', 105 | // 'ex-get-phone-pricing-number.js', 106 | // 'ex-get-pricing-number.js', 107 | // 'ex-number-get.js', 108 | // 'ex-number-search.js', 109 | // 'ex-number-buy.js', 110 | // 'ex-number-cancel.js', 111 | // 'ex-number-update.js', 112 | ]; 113 | 114 | console.log('Found', exampleFiles.length, 'examples to run:\n', exampleFiles); 115 | runNextExample(); 116 | 117 | // Run an individual example 118 | // runExample('ex-send-sms.js', console.log); 119 | // runExample('ex-number-insight-basic.js', console.log); 120 | // runExample('ex-make-call.js', console.log); 121 | // runExample('ex-get-apps.js', console.log); 122 | // runExample('ex-talk-to-call.js', console.log); 123 | // runExample('ex-dtmf-to-call.js', console.log); 124 | // runExample('ex-stream-to-call.js', console.log); 125 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nexmo", 3 | "author": "nexmo", 4 | "version": "2.9.1", 5 | "main": "lib/Nexmo", 6 | "types": "./typings/index.d.ts", 7 | "keywords": [ 8 | "sms", 9 | "voice", 10 | "nexmo", 11 | "verify", 12 | "2fa", 13 | "phone numbers" 14 | ], 15 | "homepage": "https://github.com/nexmo/nexmo-node", 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/nexmo/nexmo-node.git" 19 | }, 20 | "description": "Nexmo REST API client for Node.js. API support for SMS, Voice Calls, Text-to-Speech, Numbers, Verify (2FA) and more.", 21 | "contributors": [ 22 | "nexmo", 23 | "pvela", 24 | "leggetter", 25 | "akuzi", 26 | "bpilot", 27 | "justinfreitag", 28 | "ecwyne", 29 | "https://github.com/backhand" 30 | ], 31 | "scripts": { 32 | "compile": "./node_modules/.bin/babel -d lib src/ -s inline", 33 | "test": "npm run test-no-lint", 34 | "report-coverage": "cross-env NODE_ENV=test nyc --reporter=text-lcov mocha > coverage.lcov", 35 | "test-coverage": "cross-env NODE_ENV=test nyc mocha", 36 | "test-coverage-html": "cross-env NODE_ENV=test nyc --reporter html mocha", 37 | "test-no-lint": "npm run compile && mocha --require ./node_modules/.bin/_mocha --require @babel/register ./test/*-test.js", 38 | "test-watch": "nodemon --watch src --watch test -x 'npm run test-no-lint'", 39 | "lint": "eslint src test", 40 | "lint-fix": "eslint --fix src test", 41 | "prepublish": "npm run compile", 42 | "pretest": "npm run lint", 43 | "postinstall": "node postinstall.js" 44 | }, 45 | "devDependencies": { 46 | "@babel/cli": "^7.10.5", 47 | "@babel/core": "^7.10.5", 48 | "@babel/plugin-proposal-object-rest-spread": "^7.10.4", 49 | "@babel/preset-env": "^7.10.4", 50 | "@babel/register": "^7.10.5", 51 | "babel-plugin-istanbul": "^4.1.6", 52 | "babel-plugin-add-module-exports": "^1.0.2", 53 | "bluebird": "^3.5.3", 54 | "body-parser": "^1.18.3", 55 | "chai": "^3.5.0", 56 | "cross-env": "^5.2.0", 57 | "dotenv": "^2.0.0", 58 | "eslint": "^4.18", 59 | "eslint-config-prettier": "^6.2", 60 | "eslint-plugin-prettier": "^2.7", 61 | "expect.js": "^0.3.1", 62 | "express": "^4.16.4", 63 | "mocha": "^7.2.0", 64 | "ngrok": "^2.2.2", 65 | "nodemon": "^2.0.4", 66 | "nyc": "^14.1.1", 67 | "prettier": "^1.16.3", 68 | "sinon": "^1.17.4", 69 | "sinon-chai": "^2.8.0", 70 | "sinon-expect": "^0.3.0" 71 | }, 72 | "dependencies": { 73 | "jsonwebtoken": "^8.4.0", 74 | "request": "^2.88.0", 75 | "uuid": "^2.0.2" 76 | }, 77 | "license": "MIT" 78 | } 79 | -------------------------------------------------------------------------------- /postinstall.js: -------------------------------------------------------------------------------- 1 | console.warn( 2 | "\x1b[33m", 3 | `--------> The Vonage Server SDK and NPM package have moved! <-------- 4 | 5 | The NPM package is now is now "@vonage/server-sdk". The code repository is located at https://github.com/vonage/vonage-node-sdk. 6 | We will support this repository for 12 months, ending October 2021, with any needed bug or security fixes for the last release of v2.9.1. 7 | New features will be released under "@vonage/server-sdk", so to take advantage of those please make sure to switch to "@vonage/server-sdk" as soon as possible. 8 | `, 9 | "\x1b[0m" 10 | ); 11 | -------------------------------------------------------------------------------- /src/Account.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class Account { 4 | /** 5 | * @param {Credentials} credentials 6 | * credentials to be used when interacting with the API. 7 | * @param {Object} options 8 | * Addition Account options. 9 | */ 10 | constructor(credentials, options = {}) { 11 | this.creds = credentials; 12 | this.options = options; 13 | } 14 | 15 | /** 16 | * TODO: document 17 | */ 18 | checkBalance(callback) { 19 | return this.options.rest.get("/account/get-balance", callback); 20 | } 21 | 22 | updatePassword(newSecret, callback) { 23 | return this.options.rest.postUseQueryString( 24 | "/account/settings", 25 | { newSecret }, 26 | callback 27 | ); 28 | } 29 | 30 | updateSMSCallback(moCallBackUrl, callback) { 31 | return this.options.rest.postUseQueryString( 32 | "/account/settings", 33 | { moCallBackUrl }, 34 | callback 35 | ); 36 | } 37 | 38 | updateDeliveryReceiptCallback(drCallBackUrl, callback) { 39 | return this.options.rest.postUseQueryString( 40 | "/account/settings", 41 | { drCallBackUrl }, 42 | callback 43 | ); 44 | } 45 | 46 | topUp(trx, callback) { 47 | return this.options.rest.postUseQueryString( 48 | "/account/top-up", 49 | { trx }, 50 | callback 51 | ); 52 | } 53 | 54 | listSecrets(apiKey, callback) { 55 | return this.options.api.get( 56 | "/accounts/" + apiKey + "/secrets", 57 | {}, 58 | callback, 59 | false, 60 | true 61 | ); 62 | } 63 | 64 | getSecret(apiKey, id, callback) { 65 | return this.options.api.get( 66 | "/accounts/" + apiKey + "/secrets/" + id, 67 | {}, 68 | callback, 69 | false, 70 | true 71 | ); 72 | } 73 | 74 | createSecret(apiKey, secret, callback) { 75 | return this.options.api.postJson( 76 | "/accounts/" + apiKey + "/secrets/", 77 | { secret: secret }, 78 | callback, 79 | false, 80 | true 81 | ); 82 | } 83 | 84 | deleteSecret(apiKey, id, callback) { 85 | return this.options.api.delete( 86 | "/accounts/" + apiKey + "/secrets/" + id, 87 | callback, 88 | false, 89 | true 90 | ); 91 | } 92 | } 93 | 94 | export default Account; 95 | -------------------------------------------------------------------------------- /src/CallsResource.js: -------------------------------------------------------------------------------- 1 | import querystring from "querystring"; 2 | 3 | import StreamResource from "./StreamResource"; 4 | import TalkResource from "./TalkResource"; 5 | import DtmfResource from "./DtmfResource"; 6 | 7 | /** 8 | * Provides access to the `calls` resource. 9 | */ 10 | class CallsResource { 11 | /** 12 | * The path to the `calls` resource. 13 | */ 14 | static get PATH() { 15 | return "/v1/calls"; 16 | } 17 | 18 | /** 19 | * Creates a new CallsResource. 20 | * 21 | * @param {Credentials} creds - Credentials used when interacting with the Nexmo API. 22 | * @param {Object} options - additional options for the class. 23 | */ 24 | constructor(creds, options) { 25 | this.creds = creds; 26 | this.options = options; 27 | 28 | /** 29 | * @type StreamController 30 | */ 31 | this.stream = new StreamResource(this.creds, this.options); 32 | 33 | /** 34 | * @type TalkResource 35 | */ 36 | this.talk = new TalkResource(this.creds, this.options); 37 | 38 | /** 39 | * @type DtmfResource 40 | */ 41 | this.dtmf = new DtmfResource(this.creds, this.options); 42 | } 43 | 44 | /** 45 | * Create a new call. 46 | * 47 | * @param {Object} params - Parameters used when creating the call. See https://developer.nexmo.com/api/voice#create-an-outbound-call for more information. 48 | * @param {function} callback - function to be called when the request completes. 49 | */ 50 | create(params, callback) { 51 | params = JSON.stringify(params); 52 | 53 | var config = { 54 | host: this.options.apiHost || "api.nexmo.com", 55 | path: CallsResource.PATH, 56 | method: "POST", 57 | body: params, 58 | headers: { 59 | "Content-Type": "application/json", 60 | "Content-Length": Buffer.byteLength(params), 61 | Authorization: `Bearer ${this.creds.generateJwt()}` 62 | } 63 | }; 64 | this.options.httpClient.request(config, callback); 65 | } 66 | 67 | /** 68 | * Get an existing call. 69 | * 70 | * @param {string|object} query - The unique identifier for the call to retrieve 71 | * or a set of filter parameters for the query. For more information 72 | * see https://docs.nexmo.com/voice/voice-api/api-reference#call_retrieve 73 | * @param {function} callback - function to be called when the request completes. 74 | */ 75 | get(query, callback) { 76 | if (!query) { 77 | throw new Error('"query" is a required parameter'); 78 | } 79 | 80 | var pathExt = ""; 81 | if (typeof query === "string") { 82 | // single call Id 83 | pathExt = `/${query}`; 84 | } else if (typeof query === "object" && Object.keys(query).length > 0) { 85 | // filter 86 | pathExt = `?${querystring.stringify(query)}`; 87 | } 88 | 89 | var config = { 90 | host: this.options.apiHost || "api.nexmo.com", 91 | path: `${CallsResource.PATH}${pathExt}`, 92 | method: "GET", 93 | headers: { 94 | "Content-Type": "application/json", 95 | Authorization: `Bearer ${this.creds.generateJwt()}` 96 | } 97 | }; 98 | this.options.httpClient.request(config, callback); 99 | } 100 | 101 | /** 102 | * Update an existing call. 103 | * 104 | * @param {string} [callId] - The unique identifier for the call to update. 105 | * @param {Object} params - Parameters used when updating the call. See https://developer.nexmo.com/api/voice#modify-an-existing-call for more information. 106 | * @param {function} callback - function to be called when the request completes. 107 | */ 108 | update(callId, params, callback) { 109 | params = JSON.stringify(params); 110 | 111 | var config = { 112 | host: this.options.apiHost || "api.nexmo.com", 113 | path: `${CallsResource.PATH}/${callId}`, 114 | method: "PUT", 115 | body: params, 116 | headers: { 117 | "Content-Type": "application/json", 118 | "Content-Length": Buffer.byteLength(params), 119 | Authorization: `Bearer ${this.creds.generateJwt()}` 120 | } 121 | }; 122 | this.options.httpClient.request(config, callback); 123 | } 124 | } 125 | 126 | export default CallsResource; 127 | -------------------------------------------------------------------------------- /src/ConsoleLogger.js: -------------------------------------------------------------------------------- 1 | import NullLogger from "./NullLogger"; 2 | 3 | class ConsoleLogger extends NullLogger { 4 | constructor(consoleOverride) { 5 | super(); 6 | 7 | this.out = consoleOverride || console; 8 | } 9 | 10 | log(level, ...args) { 11 | this.out.log(`${level}:`, ...args); 12 | } 13 | 14 | warn(...args) { 15 | this.log("warn", ...args); 16 | } 17 | 18 | error(...args) { 19 | this.out.error("error:", ...args); 20 | } 21 | } 22 | 23 | export default ConsoleLogger; 24 | -------------------------------------------------------------------------------- /src/Conversion.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class Conversion { 4 | constructor(credentials, options) { 5 | this.creds = credentials; 6 | this.options = options; 7 | } 8 | 9 | voice(message_id, delivered, timestamp, callback) { 10 | return this.submit("voice", message_id, delivered, timestamp, callback); 11 | } 12 | 13 | sms(message_id, delivered, timestamp, callback) { 14 | return this.submit("sms", message_id, delivered, timestamp, callback); 15 | } 16 | 17 | submit(type, message_id, delivered, timestamp, callback) { 18 | return this.options.api.postUseQueryString( 19 | "/conversions/" + type, 20 | { "message-id": message_id, delivered, timestamp }, 21 | this.options.api._addLimitedAccessMessageToErrors(callback, 402) 22 | ); 23 | } 24 | } 25 | 26 | export default Conversion; 27 | -------------------------------------------------------------------------------- /src/Credentials.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import fs from "fs"; 4 | import JwtGenerator from "./JwtGenerator"; 5 | import HashGenerator from "./HashGenerator"; 6 | 7 | /** 8 | * Right now only key/secret credentials are supported. 9 | * However, in time JWT will also be supported. 10 | * The `Credentials` object provides an abstraction to this. 11 | * 12 | * @param {string} apiKey - A Nexmo API Key 13 | * @param {string} apiSecret - A Nexmo API Secret 14 | * @param {string} [applicationId] - A Nexmo Application ID 15 | * @param {string|Buffer} [privateKey] - When a string value is passed it should 16 | * either represent the path to the private key, or the actual 17 | * private key in string format. If a Buffer is passed then 18 | * it should be the key read from the file system. 19 | * @param {string} [signatureSecret] - A Nexmo signature Secret 20 | * @param {string} [signatureMethod] - A Nexmo compatible request signing method 21 | */ 22 | class Credentials { 23 | constructor( 24 | apiKey, 25 | apiSecret, 26 | privateKey, 27 | applicationId, 28 | signatureSecret, 29 | signatureMethod 30 | ) { 31 | this.apiKey = apiKey; 32 | this.apiSecret = apiSecret; 33 | 34 | this.privateKey = null; 35 | this.applicationId = applicationId; 36 | 37 | this.signatureSecret = signatureSecret; 38 | this.signatureMethod = signatureMethod; 39 | 40 | if (privateKey instanceof Buffer) { 41 | // it is already a buffer, use it as-is 42 | this.privateKey = privateKey; 43 | } else if ( 44 | typeof privateKey === "string" && 45 | privateKey.startsWith("-----BEGIN PRIVATE KEY-----") 46 | ) { 47 | // It's a key string. Check for \n, replace with newlines 48 | privateKey = privateKey.replace(/\\n/g, "\n"); 49 | this.privateKey = Buffer.from(privateKey, "utf-8"); 50 | } else if (privateKey !== undefined) { 51 | if (!fs.existsSync(privateKey)) { 52 | throw new Error(`File "${privateKey}" not found.`); 53 | } 54 | this.privateKey = fs.readFileSync(privateKey); 55 | } 56 | 57 | /** @private */ 58 | this._jwtGenerator = new JwtGenerator(); 59 | this._hashGenerator = new HashGenerator(); 60 | } 61 | 62 | /** 63 | * Generate a Jwt using the Private Key in the Credentials. 64 | * By default the credentials.applicationId will be used when creating the token. 65 | * However, this can be overwritten. 66 | * 67 | * @param {string} [applicationId] an application ID to be used instead of the 68 | * default Credentials.applicationId value. 69 | * 70 | * @returns {string} The generated JWT 71 | */ 72 | generateJwt( 73 | applicationId = this.applicationId, 74 | privateKey = this.privateKey 75 | ) { 76 | var claims = { 77 | application_id: applicationId 78 | }; 79 | var token = this._jwtGenerator.generate(privateKey, claims); 80 | return token; 81 | } 82 | 83 | generateSignature( 84 | params, 85 | signatureSecret = this.signatureSecret, 86 | signatureMethod = this.signatureMethod 87 | ) { 88 | return this._hashGenerator.generate( 89 | signatureMethod, 90 | signatureSecret, 91 | params 92 | ); 93 | } 94 | 95 | /** 96 | * @private 97 | * Used for testing purposes only. 98 | */ 99 | _setJwtGenerator(generator) { 100 | this._jwtGenerator = generator; 101 | } 102 | 103 | /** 104 | * @private 105 | * Used for testing purposes only. 106 | */ 107 | _setHashGenerator(generator) { 108 | this._hashGenerator = generator; 109 | } 110 | 111 | /** 112 | * Ensures a credentials instance is used. 113 | * 114 | * Key/Secret credentials are only supported at present. 115 | */ 116 | static parse(obj) { 117 | if (obj instanceof Credentials) { 118 | return obj; 119 | } else { 120 | return new Credentials( 121 | obj.apiKey, 122 | obj.apiSecret, 123 | obj.privateKey, 124 | obj.applicationId, 125 | obj.signatureSecret, 126 | obj.signatureMethod 127 | ); 128 | } 129 | } 130 | } 131 | 132 | export default Credentials; 133 | -------------------------------------------------------------------------------- /src/DtmfResource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides access to the `dtmf` resource. 3 | */ 4 | class DtmfResource { 5 | /** 6 | * The path to the `dtmf` resource. 7 | */ 8 | static get PATH() { 9 | return "/v1/calls/{call_uuid}/dtmf"; 10 | } 11 | 12 | /** 13 | * Creates a new DtmfResource. 14 | * 15 | * @param {Credentials} creds - Credentials used when interacting with the Nexmo API. 16 | * @param {Object} options - additional options for the class. 17 | */ 18 | constructor(creds, options) { 19 | this.creds = creds; 20 | this.options = options; 21 | } 22 | 23 | /** 24 | * Sends DTMF to a call. 25 | * 26 | * @param {Object} params - Parameters used when sending the dtmf to the call. See https://developer.nexmo.com/api/voice#dtmf for more information. 27 | * @param {function} callback - function to be called when the request completes. 28 | */ 29 | send(callId, params, callback) { 30 | params = JSON.stringify(params); 31 | 32 | var config = { 33 | host: this.options.apiHost || "api.nexmo.com", 34 | path: DtmfResource.PATH.replace("{call_uuid}", callId), 35 | method: "PUT", 36 | body: params, 37 | headers: { 38 | "Content-Type": "application/json", 39 | "Content-Length": Buffer.byteLength(params), 40 | Authorization: `Bearer ${this.creds.generateJwt()}` 41 | } 42 | }; 43 | this.options.httpClient.request(config, callback); 44 | } 45 | } 46 | 47 | export default DtmfResource; 48 | -------------------------------------------------------------------------------- /src/FilesResource.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require("fs"); 4 | 5 | class FilesResource { 6 | /** 7 | * The path to the `calls` resource. 8 | */ 9 | static get PATH() { 10 | return "/v1/files"; 11 | } 12 | 13 | /** 14 | * Creates a new FilesResource. 15 | * 16 | * @param {Credentials} creds - Credentials used when interacting with the Nexmo API. 17 | * @param {Object} options - additional options for the class. 18 | */ 19 | constructor(creds, options) { 20 | this.creds = creds; 21 | this.options = options; 22 | } 23 | 24 | /** 25 | * Get stream for a remote File 26 | * 27 | * @param {string} [fileIdOrUrl] - The unique identifier or URL for the file 28 | * @param {function} callback - function to be called when the request completes. 29 | */ 30 | get(fileIdOrUrl, callback) { 31 | if (!fileIdOrUrl) { 32 | throw new Error('"fileIdOrUrl" is a required parameter'); 33 | } 34 | 35 | fileIdOrUrl = fileIdOrUrl.split("/").pop(-1); 36 | 37 | var config = { 38 | host: this.options.apiHost || "api.nexmo.com", 39 | path: `${FilesResource.PATH}/${fileIdOrUrl}`, 40 | method: "GET", 41 | headers: { 42 | "Content-Type": "application/octet-stream", 43 | Authorization: `Bearer ${this.creds.generateJwt()}` 44 | } 45 | }; 46 | 47 | this.options.httpClient.request(config, callback); 48 | } 49 | 50 | /** 51 | * Save remote File locally 52 | * 53 | * @param {string} [fileIdOrUrl] - The unique identifier or URL for the file 54 | * @param {string} [file] - Filename or file descriptor 55 | * @param {function} callback - function to be called when the request completes. 56 | */ 57 | save(fileIdOrUrl, file, callback) { 58 | this.get(fileIdOrUrl, (error, data) => { 59 | if (error) { 60 | callback(error, null); 61 | } else { 62 | this.__storeFile(data, file, callback); 63 | } 64 | }); 65 | } 66 | 67 | __storeFile(data, file, callback) { 68 | fs.writeFile(file, data, error => { 69 | if (error) { 70 | callback(error, null); 71 | } else { 72 | callback(null, file); 73 | } 74 | }); 75 | } 76 | } 77 | 78 | export default FilesResource; 79 | -------------------------------------------------------------------------------- /src/HashGenerator.js: -------------------------------------------------------------------------------- 1 | const crypto = require("crypto"); 2 | 3 | class HashGenerator { 4 | /** 5 | * Generate a Signature Hash. 6 | * 7 | * @param {String} method - the method to be used when creating the hash 8 | * @param {String} secret - the secret to be used when creating the hash 9 | * @param {Object} params - params to generate hash from 10 | * 11 | * @returns {String} the generated token 12 | */ 13 | generate(method, secret, params) { 14 | params = params || {}; 15 | var signedQuery = ""; 16 | 17 | params = JSON.parse(JSON.stringify(params)); 18 | 19 | if (params.sig) { 20 | delete params.sig; 21 | } 22 | 23 | Object.keys(params) 24 | .sort() 25 | .forEach(key => { 26 | // replace & and = with _ 27 | signedQuery += "&" + key + "=" + params[key].replace(/\&|\=/g, "_"); 28 | }); 29 | 30 | var hash = ""; 31 | 32 | switch (method) { 33 | case "md5hash": 34 | signedQuery += secret; 35 | hash = crypto 36 | .createHash("md5") 37 | .update(signedQuery) 38 | .digest("hex"); 39 | break; 40 | case "md5": 41 | case "sha1": 42 | case "sha256": 43 | case "sha512": 44 | hash = crypto 45 | .createHmac(method, secret) 46 | .update(signedQuery) 47 | .digest("hex"); 48 | break; 49 | 50 | default: 51 | throw `Unknown signature algorithm: ${method}. Expected: md5hash, md5, sha1, sha256, or sha512`; 52 | } 53 | 54 | return hash; 55 | } 56 | } 57 | 58 | module.exports = HashGenerator; 59 | -------------------------------------------------------------------------------- /src/JwtGenerator.js: -------------------------------------------------------------------------------- 1 | import uuid from "uuid"; 2 | import jwt from "jsonwebtoken"; 3 | 4 | class JwtGenerator { 5 | /** 6 | * Generate a JSON Web Token (JWT). 7 | * 8 | * @param {Buffer} cert - the private key certificate to be used when signing 9 | * the claims. 10 | * @param {Object} claims - additional claims to include within the generated 11 | * JWT. 12 | * 13 | * @returns {String} the generated token 14 | */ 15 | generate(cert, claims = {}) { 16 | if (!(cert instanceof Buffer)) { 17 | throw new Error("cert must be of type Buffer"); 18 | } 19 | if (typeof claims !== "object") { 20 | throw new Error("claims must be of type object"); 21 | } 22 | 23 | var toSign = { 24 | iat: claims.issuedAt || parseInt(Date.now() / 1000, 10), 25 | jti: claims.jti || uuid.v1() 26 | }; 27 | Object.keys(claims).forEach(key => { 28 | toSign[key] = claims[key]; 29 | }); 30 | 31 | var token = jwt.sign(toSign, cert, { algorithm: "RS256" }); 32 | return token; 33 | } 34 | } 35 | 36 | module.exports = JwtGenerator; 37 | -------------------------------------------------------------------------------- /src/Media.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import fs from "fs"; 4 | import querystring from "querystring"; 5 | 6 | class Media { 7 | static get PATH() { 8 | return "/v3/media"; 9 | } 10 | 11 | constructor(credentials, options) { 12 | this.creds = credentials; 13 | this.options = options; 14 | } 15 | 16 | upload(opts, callback) { 17 | opts = opts || {}; 18 | if (!opts.file && !opts.url) { 19 | throw new Error( 20 | "You must provide either 'file' or 'url' to upload a file" 21 | ); 22 | } 23 | 24 | if (opts.file) { 25 | opts.file = fs.createReadStream(opts.file); 26 | } 27 | return this.options.api.postFile( 28 | Media.PATH, 29 | opts, 30 | function(err, response, body) { 31 | if (err) { 32 | return callback(err); 33 | } 34 | 35 | let location = ""; 36 | if (response && response.headers) { 37 | location = response.headers.location; 38 | } 39 | 40 | return callback(null, location); 41 | }, 42 | true 43 | ); 44 | } 45 | 46 | search(options, callback) { 47 | if (typeof options == "function" && !callback) { 48 | callback = options; 49 | options = {}; 50 | } 51 | 52 | options = options || {}; 53 | 54 | return this._makeRequest("GET", Media.PATH, options, {}, callback); 55 | } 56 | 57 | // If If-Modified-Since header is provided and the data hasn't changed, the 58 | // user will receive an empty body in the callback, NOT an error 59 | download(id, headers, callback) { 60 | if (!callback && typeof headers == "function") { 61 | callback = headers; 62 | headers = {}; 63 | } 64 | 65 | return this._makeRequest( 66 | "GET", 67 | `${Media.PATH}/${id}`, 68 | {}, 69 | headers, 70 | callback, 71 | true 72 | ); 73 | } 74 | 75 | delete(id, callback) { 76 | return this._makeRequest("DELETE", `${Media.PATH}/${id}`, {}, {}, callback); 77 | } 78 | 79 | get(id, callback) { 80 | return this._makeRequest( 81 | "GET", 82 | `${Media.PATH}/${id}/info`, 83 | {}, 84 | {}, 85 | callback 86 | ); 87 | } 88 | 89 | update(id, opts, callback) { 90 | return this._makeRequest( 91 | "PUT", 92 | `${Media.PATH}/${id}/info`, 93 | opts, 94 | {}, 95 | callback 96 | ); 97 | } 98 | 99 | _makeRequest(verb, path, options, headers, callback, skipJsonParsing) { 100 | headers = Object.assign( 101 | { 102 | "Content-Type": "application/json", 103 | Authorization: `Bearer ${this.creds.generateJwt()}` 104 | }, 105 | headers 106 | ); 107 | 108 | let req = {}; 109 | if (verb.toUpperCase() === "GET") { 110 | if (Object.keys(options).length) { 111 | path = path + "?" + querystring.stringify(options); 112 | } 113 | } else { 114 | req["body"] = JSON.stringify(options); 115 | } 116 | 117 | req["path"] = path; 118 | req["headers"] = headers; 119 | 120 | return this.options.api.request(req, verb, callback, skipJsonParsing); 121 | } 122 | } 123 | 124 | export default Media; 125 | -------------------------------------------------------------------------------- /src/Message.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Utils from "./Utils"; 4 | 5 | import ShortCode from "./ShortCode"; 6 | 7 | var querystring = require("querystring"); 8 | 9 | class Message { 10 | static get ERROR_MESSAGES() { 11 | return { 12 | sender: "Invalid from address", 13 | to: "Invalid to address", 14 | msg: "Invalid Text Message", 15 | body: "Invalid Body value in Binary Message", 16 | udh: "Invalid udh value in Binary Message", 17 | title: "Invalid title in WAP Push message", 18 | url: "Invalid url in WAP Push message" 19 | }; 20 | } 21 | 22 | static get PATH() { 23 | return "/sms/json"; 24 | } 25 | 26 | /** 27 | * @param {Credentials} credentials 28 | * credentials to be used when interacting with the API. 29 | * @param {Object} options 30 | * Addition SMS options. 31 | */ 32 | constructor(credentials, options = {}) { 33 | this.creds = credentials; 34 | this.options = options; 35 | 36 | var _shortcode = new ShortCode(this.creds, this.options); 37 | 38 | this.shortcodeAlert = _shortcode.shortcodeAlert.bind(_shortcode); 39 | this.shortcode2FA = _shortcode.shortcode2FA.bind(_shortcode); 40 | this.shortcodeMarketing = _shortcode.shortcodeMarketing.bind(_shortcode); 41 | } 42 | 43 | _sendRequest(endpoint, method, callback) { 44 | endpoint.path = 45 | endpoint.path + 46 | (endpoint.path.indexOf("?") > 0 ? "&" : "?") + 47 | querystring.stringify({ 48 | api_key: this.creds.apiKey, 49 | api_secret: this.creds.apiSecret 50 | }); 51 | this.options.httpClient.request(endpoint, method, callback); 52 | } 53 | 54 | _sendMessage(data, callback) { 55 | if (!data.from) { 56 | Utils.sendError(callback, new Error(Message.ERROR_MESSAGES.sender)); 57 | } else if (!data.to) { 58 | Utils.sendError(callback, new Error(Message.ERROR_MESSAGES.to)); 59 | } else { 60 | var path = Message.PATH + "?" + querystring.stringify(data); 61 | this.options.logger.info( 62 | "sending message from " + 63 | data.from + 64 | " to " + 65 | data.to + 66 | " with message " + 67 | data.text 68 | ); 69 | this._sendRequest( 70 | { 71 | host: this.options.restHost || "rest.nexmo.com", 72 | path: path 73 | }, 74 | "POST", 75 | function(err, apiResponse) { 76 | if ( 77 | !err && 78 | apiResponse.status && 79 | apiResponse.messages[0].status > 0 80 | ) { 81 | Utils.sendError( 82 | callback, 83 | new Error(apiResponse.messages[0]["error-text"]), 84 | apiResponse 85 | ); 86 | } else { 87 | if (callback) callback(err, apiResponse); 88 | } 89 | } 90 | ); 91 | } 92 | } 93 | 94 | /** 95 | * TODO: document 96 | */ 97 | sendSms(sender, recipient, message, opts, callback) { 98 | if (!message) { 99 | Utils.sendError(callback, new Error(Message.ERROR_MESSAGES.msg)); 100 | } else { 101 | if (!callback) { 102 | callback = opts; 103 | opts = {}; 104 | } 105 | opts["from"] = sender; 106 | opts["to"] = recipient; 107 | opts["text"] = message; 108 | this._sendMessage(opts, callback); 109 | } 110 | } 111 | 112 | /** 113 | * TODO: document 114 | */ 115 | sendBinaryMessage(sender, recipient, body, udh, opts, callback) { 116 | if (!body) { 117 | Utils.sendError(callback, new Error(Message.ERROR_MESSAGES.body)); 118 | } else if (!udh) { 119 | Utils.sendError(callback, new Error(Message.ERROR_MESSAGES.udh)); 120 | } else { 121 | if (!callback) { 122 | callback = opts; 123 | opts = {}; 124 | } 125 | opts["from"] = sender; 126 | opts["to"] = recipient; 127 | opts["type"] = "binary"; 128 | opts["body"] = body; 129 | opts["udh"] = udh; 130 | this._sendMessage(opts, callback); 131 | } 132 | } 133 | 134 | /** 135 | * TODO: document 136 | */ 137 | sendWapPushMessage(sender, recipient, title, url, validity, opts, callback) { 138 | if (!title) { 139 | Utils.sendError(callback, new Error(Message.ERROR_MESSAGES.title)); 140 | } else if (!url) { 141 | Utils.sendError(callback, new Error(Message.ERROR_MESSAGES.url)); 142 | } else { 143 | if (typeof validity === "function") { 144 | callback = validity; 145 | opts = {}; 146 | validity = 86400000; 147 | } 148 | if (typeof opts === "function") { 149 | callback = opts; 150 | opts = {}; 151 | } 152 | opts["from"] = sender; 153 | opts["to"] = recipient; 154 | opts["type"] = "wappush"; 155 | opts["title"] = title; 156 | opts["validity"] = validity; 157 | opts["url"] = url; 158 | this._sendMessage(opts, callback); 159 | } 160 | } 161 | 162 | search(id, callback) { 163 | if (typeof id == "string") { 164 | return this.options.rest.get( 165 | "/search/message", 166 | { 167 | id: id 168 | }, 169 | callback 170 | ); 171 | } 172 | 173 | // Otherwise we expect an array 174 | return this.options.rest.get( 175 | "/search/messages", 176 | { 177 | ids: id 178 | }, 179 | callback 180 | ); 181 | } 182 | 183 | searchRejections(to, date, callback) { 184 | return this.options.rest.get( 185 | "/search/rejections", 186 | { 187 | to, 188 | date 189 | }, 190 | callback 191 | ); 192 | } 193 | } 194 | 195 | export default Message; 196 | -------------------------------------------------------------------------------- /src/Nexmo.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | 4 | import Credentials from "./Credentials"; 5 | import JwtGenerator from "./JwtGenerator"; 6 | import HashGenerator from "./HashGenerator"; 7 | import Message from "./Message"; 8 | import Voice from "./Voice"; 9 | import Number from "./Number"; 10 | import Verify from "./Verify"; 11 | import NumberInsight from "./NumberInsight"; 12 | import App from "./App"; 13 | import Account from "./Account"; 14 | import CallsResource from "./CallsResource"; 15 | import FilesResource from "./FilesResource"; 16 | import Conversion from "./Conversion"; 17 | import Media from "./Media"; 18 | import Redact from "./Redact"; 19 | import Pricing from "./Pricing"; 20 | import HttpClient from "./HttpClient"; 21 | import NullLogger from "./NullLogger"; 22 | import ConsoleLogger from "./ConsoleLogger"; 23 | 24 | const jwtGeneratorInstance = new JwtGenerator(); 25 | const hashGeneratorInstance = new HashGenerator(); 26 | 27 | class Nexmo { 28 | /** 29 | * @param {Credentials} credentials - Nexmo API credentials 30 | * @param {string} credentials.apiKey - the Nexmo API key 31 | * @param {string} credentials.apiSecret - the Nexmo API secret 32 | * @param {Object} options - Additional options 33 | * @param {boolean} options.debug - `true` to turn on debug logging 34 | * @param {Object} options.logger - Set a custom logger. 35 | * @param {string} options.appendToUserAgent - A value to append to the user agent. 36 | * The value will be prefixed with a `/` 37 | */ 38 | constructor(credentials, options = { debug: false }) { 39 | this.credentials = Credentials.parse(credentials); 40 | this.options = Object.assign({}, options); 41 | 42 | // If no logger has been supplied but debug has been set 43 | // default to using the ConsoleLogger 44 | if (!this.options.logger && this.options.debug) { 45 | this.options.logger = new ConsoleLogger(); 46 | } else if (!this.options.logger) { 47 | // Swallow the logging 48 | this.options.logger = new NullLogger(); 49 | } 50 | 51 | let userAgent = "nexmo-node/UNKNOWN node/UNKNOWN"; 52 | try { 53 | var packageDetails = require(path.join(__dirname, "..", "package.json")); 54 | userAgent = `nexmo-node/${ 55 | packageDetails.version 56 | } node/${process.version.replace("v", "")}`; 57 | } catch (e) { 58 | console.warn("Could not load package details"); 59 | } 60 | this.options.userAgent = userAgent; 61 | if (this.options.appendToUserAgent) { 62 | this.options.userAgent += ` ${this.options.appendToUserAgent}`; 63 | } 64 | 65 | // This is legacy, everything should use rest or api going forward 66 | this.options.httpClient = new HttpClient( 67 | Object.assign( 68 | { host: this.options.restHost || "rest.nexmo.com" }, 69 | this.options 70 | ), 71 | this.credentials 72 | ); 73 | 74 | // We have two different hosts, so we use two different HttpClients 75 | this.options.api = new HttpClient( 76 | Object.assign( 77 | { host: this.options.apiHost || "api.nexmo.com" }, 78 | this.options 79 | ), 80 | this.credentials 81 | ); 82 | this.options.rest = new HttpClient( 83 | Object.assign( 84 | { host: this.options.restHost || "rest.nexmo.com" }, 85 | this.options 86 | ), 87 | this.credentials 88 | ); 89 | 90 | this.message = new Message(this.credentials, this.options); 91 | this.voice = new Voice(this.credentials, this.options); 92 | this.number = new Number(this.credentials, this.options); 93 | this.verify = new Verify(this.credentials, this.options); 94 | this.numberInsight = new NumberInsight(this.credentials, this.options); 95 | this.applications = new App(this.credentials, this.options); 96 | this.account = new Account(this.credentials, this.options); 97 | this.calls = new CallsResource(this.credentials, this.options); 98 | this.files = new FilesResource(this.credentials, this.options); 99 | this.conversion = new Conversion(this.credentials, this.options); 100 | this.media = new Media(this.credentials, this.options); 101 | this.redact = new Redact(this.credentials, this.options); 102 | this.pricing = new Pricing(this.credentials, this.options); 103 | 104 | /** 105 | * @deprecated Please use nexmo.applications 106 | */ 107 | this.app = this.applications; 108 | } 109 | 110 | /** 111 | * Generate a JSON Web Token (JWT). 112 | * 113 | * The private key used upon Nexmo instance construction will be used to sign 114 | * the JWT. The application_id you used upon Nexmo instance creation will be 115 | * included in the claims for the JWT, however this can be overridden by passing 116 | * an application_id as part of the claims. 117 | * 118 | * @param {Object} claims - name/value pair claims to sign within the JWT 119 | * 120 | * @returns {String} the generated token 121 | */ 122 | 123 | generateJwt(claims = {}) { 124 | if (claims.application_id === undefined) { 125 | claims.application_id = this.credentials.applicationId; 126 | } 127 | return Nexmo.generateJwt(this.credentials.privateKey, claims); 128 | } 129 | 130 | /** 131 | * Generate a Signature Hash. 132 | * 133 | * @param {Object} params - params to generate hash from 134 | * 135 | * @returns {String} the generated token 136 | */ 137 | generateSignature(params) { 138 | return this.credentials.generateSignature(params); 139 | } 140 | } 141 | 142 | /** 143 | * Generate a JSON Web Token (JWT). 144 | * 145 | * @param {String|Buffer} privateKey - the path to the private key certificate 146 | * to be used when signing the claims. 147 | * @param {Object} claims - name/value pair claims to sign within the JWT 148 | * 149 | * @returns {String} the generated token 150 | */ 151 | Nexmo.generateJwt = (privateKey, claims) => { 152 | if (!(privateKey instanceof Buffer)) { 153 | if (!fs.existsSync(privateKey)) { 154 | throw new Error(`File "${privateKey}" not found.`); 155 | } else { 156 | privateKey = fs.readFileSync(privateKey); 157 | } 158 | } 159 | return jwtGeneratorInstance.generate(privateKey, claims); 160 | }; 161 | 162 | /** 163 | * Generate a Signature Hash. 164 | * 165 | * @param {String} method - the method to be used when creating the hash 166 | * @param {String} secret - the secret to be used when creating the hash 167 | * @param {Object} params - params to generate hash from 168 | * 169 | * @returns {String} the generated token 170 | */ 171 | Nexmo.generateSignature = (method, secret, params) => { 172 | return hashGeneratorInstance.generate(method, secret, params); 173 | }; 174 | 175 | export default Nexmo; 176 | -------------------------------------------------------------------------------- /src/NullLogger.js: -------------------------------------------------------------------------------- 1 | class NullLogger { 2 | log(level, ...args) {} 3 | 4 | info(...args) { 5 | this.log("info", ...args); 6 | } 7 | 8 | warn(...args) { 9 | this.log("warn", ...args); 10 | } 11 | 12 | error(...args) { 13 | this.log("error", ...args); 14 | } 15 | } 16 | 17 | module.exports = NullLogger; 18 | -------------------------------------------------------------------------------- /src/Number.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Pricing from "./Pricing"; 4 | 5 | import Utils from "./Utils"; 6 | 7 | class Number { 8 | static get PATH() { 9 | return "/number"; 10 | } 11 | 12 | static get ERROR_MESSAGES() { 13 | return { 14 | optionsNotAnObject: 15 | "Options parameter should be a dictionary. Check the docs for valid properties for options", 16 | countrycode: "Invalid Country Code", 17 | msisdn: "Invalid MSISDN passed" 18 | }; 19 | } 20 | /** 21 | * @param {Credentials} credentials 22 | * credentials to be used when interacting with the API. 23 | * @param {Object} options 24 | * Addition Number options. 25 | */ 26 | constructor(credentials, options = {}) { 27 | this.creds = credentials; 28 | this.options = options; 29 | 30 | this._pricing = new Pricing(credentials, options); 31 | } 32 | 33 | /** 34 | * TODO: remove with next major release 35 | */ 36 | getPricing() { 37 | this._pricing.get.apply(this, arguments); 38 | } 39 | 40 | /** 41 | * TODO: remove with next major release 42 | */ 43 | getPhonePricing() { 44 | this._pricing.getPhone.apply(this, arguments); 45 | } 46 | 47 | /** 48 | * TODO: document 49 | */ 50 | get(options, callback) { 51 | if (typeof options === "function") { 52 | callback = options; 53 | options = {}; 54 | } else if (typeof options !== "object") { 55 | Utils.sendError( 56 | callback, 57 | new Error(Number.ERROR_MESSAGES.optionsNotAnObject) 58 | ); 59 | } 60 | 61 | options.api_key = options.api_key || this.creds.apiKey; 62 | options.api_secret = options.api_secret || this.creds.apiSecret; 63 | 64 | this.options.httpClient.request( 65 | { 66 | path: Utils.createPathWithQuery(`/account${Number.PATH}s`, options) 67 | }, 68 | callback 69 | ); 70 | } 71 | 72 | /** 73 | * TODO: document 74 | */ 75 | search(countryCode, pattern, callback) { 76 | let params = { 77 | api_key: this.creds.apiKey, 78 | api_secret: this.creds.apiSecret 79 | }; 80 | if (!countryCode || countryCode.length !== 2) { 81 | Utils.sendError(callback, new Error(Number.ERROR_MESSAGES.countrycode)); 82 | } else { 83 | params["country"] = countryCode; 84 | if (typeof pattern === "function") { 85 | callback = pattern; 86 | } else if (typeof pattern === "object") { 87 | for (var arg in pattern) { 88 | params[arg] = pattern[arg]; 89 | } 90 | } else { 91 | params["pattern"] = pattern; 92 | } 93 | this.options.httpClient.request( 94 | { 95 | path: Utils.createPathWithQuery(`${Number.PATH}/search`, params) 96 | }, 97 | callback 98 | ); 99 | } 100 | } 101 | 102 | /** 103 | * TODO: document 104 | */ 105 | buy(countryCode, msisdn, targetApiKey, callback) { 106 | if (!countryCode || countryCode.length !== 2) { 107 | Utils.sendError(callback, new Error(Number.ERROR_MESSAGES.countrycode)); 108 | } else if (!msisdn) { 109 | Utils.sendError(callback, new Error(Number.ERROR_MESSAGES.msisdn)); 110 | } else { 111 | let opts = { 112 | country: countryCode, 113 | msisdn, 114 | api_key: this.creds.apiKey, 115 | api_secret: this.creds.apiSecret 116 | }; 117 | 118 | if (targetApiKey instanceof Function) { 119 | callback = targetApiKey; 120 | } else { 121 | opts.target_api_key = targetApiKey; 122 | } 123 | 124 | this.options.httpClient.request( 125 | { 126 | path: Utils.createPathWithQuery(`${Number.PATH}/buy`, opts) 127 | }, 128 | "POST", 129 | callback 130 | ); 131 | } 132 | } 133 | 134 | /** 135 | * TODO: document 136 | */ 137 | cancel(countryCode, msisdn, targetApiKey, callback) { 138 | if (!countryCode || countryCode.length !== 2) { 139 | Utils.sendError(callback, new Error(Number.ERROR_MESSAGES.countrycode)); 140 | } else if (!msisdn) { 141 | Utils.sendError(callback, new Error(Number.ERROR_MESSAGES.msisdn)); 142 | } else { 143 | let opts = { 144 | country: countryCode, 145 | msisdn, 146 | api_key: this.creds.apiKey, 147 | api_secret: this.creds.apiSecret 148 | }; 149 | 150 | if (targetApiKey instanceof Function) { 151 | callback = targetApiKey; 152 | } else { 153 | opts.target_api_key = targetApiKey; 154 | } 155 | 156 | this.options.httpClient.request( 157 | { 158 | path: Utils.createPathWithQuery(`${Number.PATH}/cancel`, opts) 159 | }, 160 | "POST", 161 | callback 162 | ); 163 | } 164 | } 165 | 166 | /** 167 | * TODO: document 168 | */ 169 | update(countryCode, msisdn, params, callback) { 170 | if (!countryCode || countryCode.length !== 2) { 171 | Utils.sendError(callback, new Error(Number.ERROR_MESSAGES.countrycode)); 172 | } else if (!msisdn) { 173 | Utils.sendError(callback, new Error(Number.ERROR_MESSAGES.msisdn)); 174 | } else { 175 | params["country"] = countryCode; 176 | params["msisdn"] = msisdn; 177 | params["api_key"] = this.creds.apiKey; 178 | params["api_secret"] = this.creds.apiSecret; 179 | 180 | this.options.httpClient.request( 181 | { 182 | path: Utils.createPathWithQuery(`${Number.PATH}/update`, params) 183 | }, 184 | "POST", 185 | callback 186 | ); 187 | } 188 | } 189 | } 190 | 191 | export default Number; 192 | -------------------------------------------------------------------------------- /src/Pricing.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Utils from "./Utils"; 4 | 5 | class Pricing { 6 | static get PATH() { 7 | return "/account/{endpoint}/outbound/{type}"; 8 | } 9 | 10 | constructor(credentials, options) { 11 | this.creds = credentials; 12 | this.options = options; 13 | } 14 | 15 | get(type, country, callback) { 16 | return this.options.rest.get( 17 | Pricing.PATH.replace("{endpoint}", "get-pricing").replace("{type}", type), 18 | { country }, 19 | callback 20 | ); 21 | } 22 | 23 | getFull(type, callback) { 24 | return this.options.rest.get( 25 | Pricing.PATH.replace("{endpoint}", "get-full-pricing").replace( 26 | "{type}", 27 | type 28 | ), 29 | callback 30 | ); 31 | } 32 | 33 | getPrefix(type, prefix, callback) { 34 | return this.options.rest.get( 35 | Pricing.PATH.replace("{endpoint}", "get-prefix-pricing").replace( 36 | "{type}", 37 | type 38 | ), 39 | { prefix }, 40 | callback 41 | ); 42 | } 43 | 44 | getPhone(type, phone, callback) { 45 | return this.options.rest.get( 46 | Pricing.PATH.replace("{endpoint}", "get-phone-pricing").replace( 47 | "{type}", 48 | type 49 | ), 50 | { phone }, 51 | callback 52 | ); 53 | } 54 | } 55 | 56 | export default Pricing; 57 | -------------------------------------------------------------------------------- /src/Redact.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import fs from "fs"; 4 | import querystring from "querystring"; 5 | 6 | class Redact { 7 | static get PATH() { 8 | return "/v1/redact"; 9 | } 10 | 11 | constructor(credentials, options) { 12 | this.creds = credentials; 13 | this.options = options; 14 | } 15 | 16 | transaction(id, product, opts, callback) { 17 | if (typeof callback === "undefined" && typeof opts === "function") { 18 | callback = opts; 19 | opts = {}; 20 | } 21 | 22 | opts = opts || {}; 23 | 24 | return this.options.api.postJson( 25 | `${Redact.PATH}/transaction`, 26 | { id, product, ...opts }, 27 | function(err, response, body) { 28 | if (err) { 29 | return callback(err); 30 | } 31 | 32 | return callback(null, body); 33 | } 34 | ); 35 | } 36 | } 37 | 38 | export default Redact; 39 | -------------------------------------------------------------------------------- /src/ShortCode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Utils from "./Utils"; 4 | 5 | var querystring = require("querystring"); 6 | 7 | /** 8 | * Provides access to the `ShortCode` endpoint. 9 | */ 10 | class ShortCode { 11 | static get PATH() { 12 | return "/sc/us/${type}/json"; 13 | } 14 | 15 | static get ERROR_MESSAGES() { 16 | return { 17 | to: "Invalid to address", 18 | msgParams: "Invalid shortcode message parameters" 19 | }; 20 | } 21 | 22 | /** 23 | * @param {Credentials} credentials 24 | * credentials to be used when interacting with the API. 25 | * @param {Object} options 26 | * Additional ShortCode options. 27 | */ 28 | constructor(credentials, options = {}) { 29 | this.creds = credentials; 30 | this.options = options; 31 | } 32 | 33 | _sendRequest(endpoint, method, callback) { 34 | endpoint.path = 35 | endpoint.path + 36 | (endpoint.path.indexOf("?") > 0 ? "&" : "?") + 37 | querystring.stringify({ 38 | api_key: this.creds.apiKey, 39 | api_secret: this.creds.apiSecret 40 | }); 41 | this.options.httpClient.request(endpoint, method, callback); 42 | } 43 | 44 | _sendViaShortcode(type, recipient, messageParams, opts, callback) { 45 | if (!recipient) { 46 | Utils.sendError(callback, new Error(ShortCode.ERROR_MESSAGES.to)); 47 | } 48 | if (!messageParams || !Object.keys(messageParams)) { 49 | Utils.sendError(callback, new Error(ShortCode.ERROR_MESSAGES.msgParams)); 50 | } 51 | opts = opts || {}; 52 | var path = ShortCode.PATH.replace("${type}", type); 53 | Object.keys(messageParams).forEach(function(key) { 54 | opts[key] = messageParams[key]; 55 | }); 56 | opts.to = recipient; 57 | path += "?" + querystring.stringify(opts); 58 | this.options.logger.info( 59 | "sending message from shortcode " + 60 | type + 61 | " to " + 62 | recipient + 63 | " with parameters " + 64 | JSON.stringify(messageParams) 65 | ); 66 | this._sendRequest( 67 | { 68 | host: this.options.restHost || "rest.nexmo.com", 69 | path: path 70 | }, 71 | "POST", 72 | function(err, apiResponse) { 73 | if (!err && apiResponse.status && apiResponse.messages[0].status > 0) { 74 | Utils.sendError( 75 | callback, 76 | new Error(apiResponse.messages[0]["error-text"]), 77 | apiResponse 78 | ); 79 | } else { 80 | if (callback) callback(err, apiResponse); 81 | } 82 | } 83 | ); 84 | } 85 | 86 | shortcodeAlert(recipient, messageParams, opts, callback) { 87 | this._sendViaShortcode("alert", recipient, messageParams, opts, callback); 88 | } 89 | shortcode2FA(recipient, messageParams, opts, callback) { 90 | this._sendViaShortcode("2fa", recipient, messageParams, opts, callback); 91 | } 92 | shortcodeMarketing(recipient, messageParams, opts, callback) { 93 | this._sendViaShortcode( 94 | "marketing", 95 | recipient, 96 | messageParams, 97 | opts, 98 | callback 99 | ); 100 | } 101 | } 102 | 103 | export default ShortCode; 104 | -------------------------------------------------------------------------------- /src/StreamResource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides access to the `stream` resource. 3 | */ 4 | class StreamResource { 5 | /** 6 | * The path to the `stream` resource. 7 | */ 8 | static get PATH() { 9 | return "/v1/calls/{call_uuid}/stream"; 10 | } 11 | 12 | /** 13 | * Creates a new StreamResource. 14 | * 15 | * @param {Credentials} creds - Credentials used when interacting with the Nexmo API. 16 | * @param {Object} options - additional options for the class. 17 | */ 18 | constructor(creds, options) { 19 | this.creds = creds; 20 | this.options = options; 21 | } 22 | 23 | /** 24 | * Starts a stream in a call. 25 | * 26 | * @param {Object} params - Parameters used when starting the stream. See https://developer.nexmo.com/api/voice#stream for more information. 27 | * @param {function} callback - function to be called when the request completes. 28 | */ 29 | start(callId, params, callback) { 30 | params = JSON.stringify(params); 31 | 32 | var config = { 33 | host: this.options.apiHost || "api.nexmo.com", 34 | path: StreamResource.PATH.replace("{call_uuid}", callId), 35 | method: "PUT", 36 | body: params, 37 | headers: { 38 | "Content-Type": "application/json", 39 | "Content-Length": Buffer.byteLength(params), 40 | Authorization: `Bearer ${this.creds.generateJwt()}` 41 | } 42 | }; 43 | this.options.httpClient.request(config, callback); 44 | } 45 | 46 | /** 47 | * Stop a stream in a call. 48 | * 49 | * @param {string} callId - The unique identifier for the call for the stream to be stopped in. 50 | * @param {function} callback - function to be called when the request completes. 51 | */ 52 | stop(callId, callback) { 53 | var config = { 54 | host: this.options.apiHost || "api.nexmo.com", 55 | path: StreamResource.PATH.replace("{call_uuid}", callId), 56 | method: "DELETE", 57 | headers: { 58 | "Content-Type": "application/json", 59 | Authorization: `Bearer ${this.creds.generateJwt()}` 60 | } 61 | }; 62 | this.options.httpClient.request(config, callback); 63 | } 64 | } 65 | 66 | export default StreamResource; 67 | -------------------------------------------------------------------------------- /src/TalkResource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides access to the `talk` resource. 3 | */ 4 | class TalkResource { 5 | /** 6 | * The path to the `talk` resource. 7 | */ 8 | static get PATH() { 9 | return "/v1/calls/{call_uuid}/talk"; 10 | } 11 | 12 | /** 13 | * Creates a new TalkResource. 14 | * 15 | * @param {Credentials} creds - Credentials used when interacting with the Nexmo API. 16 | * @param {Object} options - additional options for the class. 17 | */ 18 | constructor(creds, options) { 19 | this.creds = creds; 20 | this.options = options; 21 | } 22 | 23 | /** 24 | * Starts a talk in a call. 25 | * 26 | * @param {Object} params - Parameters used when starting the talk. See https://developer.nexmo.com/api/voice#talk for more information. 27 | * @param {function} callback - function to be called when the request completes. 28 | */ 29 | start(callId, params, callback) { 30 | params = JSON.stringify(params); 31 | 32 | var config = { 33 | host: this.options.apiHost || "api.nexmo.com", 34 | path: TalkResource.PATH.replace("{call_uuid}", callId), 35 | method: "PUT", 36 | body: params, 37 | headers: { 38 | "Content-Type": "application/json", 39 | "Content-Length": Buffer.byteLength(params), 40 | Authorization: `Bearer ${this.creds.generateJwt()}` 41 | } 42 | }; 43 | this.options.httpClient.request(config, callback); 44 | } 45 | 46 | /** 47 | * Stop a talk in a call. 48 | * 49 | * @param {string} callId - The unique identifier for the call for the talk to be stopped in. 50 | * @param {function} callback - function to be called when the request completes. 51 | */ 52 | stop(callId, callback) { 53 | var config = { 54 | host: this.options.apiHost || "api.nexmo.com", 55 | path: TalkResource.PATH.replace("{call_uuid}", callId), 56 | method: "DELETE", 57 | headers: { 58 | "Content-Type": "application/json", 59 | Authorization: `Bearer ${this.creds.generateJwt()}` 60 | } 61 | }; 62 | this.options.httpClient.request(config, callback); 63 | } 64 | } 65 | 66 | export default TalkResource; 67 | -------------------------------------------------------------------------------- /src/Utils.js: -------------------------------------------------------------------------------- 1 | var querystring = require("querystring"); 2 | 3 | exports.createPathWithQuery = function(path, query) { 4 | if (!query) { 5 | throw new Error('"query" is a required parameter'); 6 | } 7 | 8 | var pathExt = ""; 9 | if (typeof query === "string") { 10 | // single call Id 11 | pathExt = `/${query}`; 12 | } else if (typeof query === "object" && Object.keys(query).length > 0) { 13 | // filter 14 | pathExt = `?${querystring.stringify(query)}`; 15 | } 16 | 17 | return `${path}${pathExt}`; 18 | }; 19 | 20 | exports.sendError = function(callback, err, returnData) { 21 | // Throw the error in case if there is no callback passed 22 | if (callback) { 23 | callback(err, returnData); 24 | } else { 25 | throw err; 26 | } 27 | }; 28 | 29 | exports.clone = function(a) { 30 | return JSON.parse(JSON.stringify(a)); 31 | }; 32 | -------------------------------------------------------------------------------- /src/Verify.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Utils from "./Utils"; 4 | 5 | class Verify { 6 | static get PATH() { 7 | return "/verify{action}/json"; 8 | } 9 | 10 | static get ERROR_MESSAGES() { 11 | return { 12 | verifyValidation: "Missing Mandatory fields (number and/or brand)", 13 | checkVerifyValidation: 14 | "Missing Mandatory fields (request_id and/or code)", 15 | controlVerifyValidation: 16 | "Missing Mandatory fields (request_id and/or cmd-command)", 17 | searchVerifyValidation: 18 | "Missing Mandatory fields (request_id or request_ids)" 19 | }; 20 | } 21 | /** 22 | * @param {Credentials} credentials 23 | * credentials to be used when interacting with the API. 24 | * @param {Object} options 25 | * Addition Verify options. 26 | */ 27 | constructor(credentials, options = {}) { 28 | this.creds = credentials; 29 | this.options = options; 30 | } 31 | 32 | /** 33 | * TODO: document 34 | */ 35 | request(inputParams, callback) { 36 | if (!inputParams.number || !inputParams.brand) { 37 | Utils.sendError( 38 | callback, 39 | new Error(Verify.ERROR_MESSAGES.verifyValidation) 40 | ); 41 | } else { 42 | inputParams["api_key"] = this.creds.apiKey; 43 | inputParams["api_secret"] = this.creds.apiSecret; 44 | this.options.httpClient.request( 45 | { 46 | host: this.options.apiHost || "api.nexmo.com", 47 | path: Utils.createPathWithQuery( 48 | `${Verify.PATH.replace("{action}", "")}`, 49 | inputParams 50 | ) 51 | }, 52 | callback 53 | ); 54 | } 55 | } 56 | 57 | /** 58 | * TODO: document 59 | */ 60 | psd2(inputParams, callback) { 61 | inputParams["api_key"] = this.creds.apiKey; 62 | inputParams["api_secret"] = this.creds.apiSecret; 63 | this.options.httpClient.request( 64 | { 65 | host: this.options.apiHost || "api.nexmo.com", 66 | path: Utils.createPathWithQuery( 67 | `${Verify.PATH.replace("{action}", "/psd2")}`, 68 | inputParams 69 | ) 70 | }, 71 | callback 72 | ); 73 | } 74 | 75 | /** 76 | * TODO: document 77 | */ 78 | check(inputParams, callback) { 79 | if (!inputParams.request_id || !inputParams.code) { 80 | Utils.sendError( 81 | callback, 82 | new Error(Verify.ERROR_MESSAGES.checkVerifyValidation) 83 | ); 84 | } else { 85 | inputParams["api_key"] = this.creds.apiKey; 86 | inputParams["api_secret"] = this.creds.apiSecret; 87 | this.options.httpClient.request( 88 | { 89 | host: this.options.apiHost || "api.nexmo.com", 90 | path: Utils.createPathWithQuery( 91 | `${Verify.PATH.replace("{action}", "/check")}`, 92 | inputParams 93 | ) 94 | }, 95 | callback 96 | ); 97 | } 98 | } 99 | 100 | /** 101 | * TODO: document 102 | */ 103 | control(inputParams, callback) { 104 | if (!inputParams.request_id || !inputParams.cmd) { 105 | Utils.sendError( 106 | callback, 107 | new Error(Verify.ERROR_MESSAGES.controlVerifyValidation) 108 | ); 109 | } else { 110 | inputParams["api_key"] = this.creds.apiKey; 111 | inputParams["api_secret"] = this.creds.apiSecret; 112 | this.options.httpClient.request( 113 | { 114 | host: this.options.apiHost || "api.nexmo.com", 115 | path: Utils.createPathWithQuery( 116 | `${Verify.PATH.replace("{action}", "/control")}`, 117 | inputParams 118 | ) 119 | }, 120 | callback 121 | ); 122 | } 123 | } 124 | 125 | /** 126 | * TODO: document 127 | */ 128 | search(requestIds, callback) { 129 | var requestIdParam = {}; 130 | if (!requestIds) { 131 | Utils.sendError( 132 | callback, 133 | new Error(Verify.ERROR_MESSAGES.searchVerifyValidation) 134 | ); 135 | } else { 136 | if (Array.isArray(requestIds)) { 137 | if (requestIds.length === 1) { 138 | requestIdParam.request_id = requestIds; 139 | } else { 140 | requestIdParam.request_ids = requestIds; 141 | } 142 | } else { 143 | requestIdParam.request_id = requestIds; 144 | } 145 | requestIdParam["api_key"] = this.creds.apiKey; 146 | requestIdParam["api_secret"] = this.creds.apiSecret; 147 | this.options.httpClient.request( 148 | { 149 | host: this.options.apiHost || "api.nexmo.com", 150 | path: Utils.createPathWithQuery( 151 | `${Verify.PATH.replace("{action}", "/search")}`, 152 | requestIdParam 153 | ) 154 | }, 155 | callback 156 | ); 157 | } 158 | } 159 | } 160 | 161 | export default Verify; 162 | -------------------------------------------------------------------------------- /src/Voice.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Utils from "./Utils"; 4 | 5 | class Voice { 6 | static get ERROR_MESSAGES() { 7 | return { 8 | to: "Invalid to address", 9 | msg: "Invalid Text Message", 10 | maxDigits: "Invalid max digits for TTS prompt", 11 | byeText: "Invalid bye text for TTS prompt", 12 | pinCode: "Invalid pin code for TTS confirm", 13 | failedText: "Invalid failed text for TTS confirm", 14 | answerUrl: "Invalid answer URL for call" 15 | }; 16 | } 17 | /** 18 | * @param {Credentials} credentials 19 | * credentials to be used when interacting with the API. 20 | * @param {Object} options 21 | * Addition options. 22 | */ 23 | constructor(credentials, options = {}) { 24 | this.creds = credentials; 25 | this.options = options; 26 | } 27 | 28 | _sendVoiceMessage(endpoint, data, callback) { 29 | if (!data.to) { 30 | Utils.sendError(callback, new Error(Voice.ERROR_MESSAGES.to)); 31 | } else { 32 | data["api_key"] = this.creds.apiKey; 33 | data["api_secret"] = this.creds.apiSecret; 34 | this.options.logger.info( 35 | "sending TTS message to " + data.to + " with message " + data.text 36 | ); 37 | this.options.httpClient.request( 38 | { 39 | host: endpoint.host, 40 | path: Utils.createPathWithQuery(endpoint.path, data) 41 | }, 42 | "POST", 43 | (err, apiResponse) => { 44 | if (!err && apiResponse.status && apiResponse.status > 0) { 45 | Utils.sendError( 46 | callback, 47 | new Error(apiResponse["error-text"]), 48 | apiResponse 49 | ); 50 | } else { 51 | if (callback) callback(err, apiResponse); 52 | } 53 | } 54 | ); 55 | } 56 | } 57 | 58 | /** 59 | * TODO: document 60 | */ 61 | sendTTSMessage(recipient, message, opts, callback) { 62 | if (!message) { 63 | Utils.sendError(callback, new Error(Voice.ERROR_MESSAGES.msg)); 64 | } else { 65 | if (!opts) { 66 | opts = {}; 67 | } 68 | opts["to"] = recipient; 69 | opts["text"] = message; 70 | this._sendVoiceMessage( 71 | { 72 | host: this.options.apiHost || "api.nexmo.com", 73 | path: "/tts/json" 74 | }, 75 | opts, 76 | callback 77 | ); 78 | } 79 | } 80 | 81 | /** 82 | * TODO: remove with next major version, API is 404 83 | */ 84 | sendTTSPromptWithCapture( 85 | recipient, 86 | message, 87 | maxDigits, 88 | byeText, 89 | opts, 90 | callback 91 | ) { 92 | if (!message) { 93 | Utils.sendError(callback, new Error(Voice.ERROR_MESSAGES.msg)); 94 | } else if (!maxDigits || isNaN(maxDigits) || maxDigits.length > 16) { 95 | Utils.sendError(callback, new Error(Voice.ERROR_MESSAGES.maxDigits)); 96 | } else if (!byeText) { 97 | Utils.sendError(callback, new Error(Voice.ERROR_MESSAGES.byeText)); 98 | } else { 99 | if (!opts) { 100 | opts = {}; 101 | } 102 | opts["to"] = recipient; 103 | opts["text"] = message; 104 | opts["max_digits"] = maxDigits; 105 | opts["bye_text"] = byeText; 106 | this._sendVoiceMessage( 107 | { 108 | host: this.options.apiHost || "api.nexmo.com", 109 | path: "/tts-prompt/json" 110 | }, 111 | opts, 112 | callback 113 | ); 114 | } 115 | } 116 | 117 | /** 118 | * TODO: remove with next major version, API is 404 119 | */ 120 | sendTTSPromptWithConfirm( 121 | recipient, 122 | message, 123 | maxDigits, 124 | pinCode, 125 | byeText, 126 | failedText, 127 | opts, 128 | callback 129 | ) { 130 | if (!message) { 131 | Utils.sendError(callback, new Error(Voice.ERROR_MESSAGES.msg)); 132 | } else if (!maxDigits || isNaN(maxDigits) || maxDigits.length > 16) { 133 | Utils.sendError(callback, new Error(Voice.ERROR_MESSAGES.maxDigits)); 134 | } else if (!pinCode || pinCode.length !== maxDigits) { 135 | Utils.sendError(callback, new Error(Voice.ERROR_MESSAGES.pinCode)); 136 | } else if (!byeText) { 137 | Utils.sendError(callback, new Error(Voice.ERROR_MESSAGES.byeText)); 138 | } else if (!failedText) { 139 | Utils.sendError(callback, new Error(Voice.ERROR_MESSAGES.failedText)); 140 | } else { 141 | if (!opts) { 142 | opts = {}; 143 | } 144 | opts["to"] = recipient; 145 | opts["text"] = message; 146 | opts["max_digits"] = maxDigits; 147 | opts["pin_code"] = pinCode; 148 | opts["bye_text"] = byeText; 149 | opts["failed_text"] = failedText; 150 | this._sendVoiceMessage( 151 | { 152 | host: this.options.apiHost || "api.nexmo.com", 153 | path: "/tts-prompt/json" 154 | }, 155 | opts, 156 | callback 157 | ); 158 | } 159 | } 160 | 161 | /** 162 | * TODO: remove with next major version, API is 404 163 | */ 164 | call(recipient, answerUrl, opts, callback) { 165 | if (!answerUrl) { 166 | Utils.sendError(callback, new Error(Voice.ERROR_MESSAGES.answerUrl)); 167 | } else { 168 | if (!opts) { 169 | opts = {}; 170 | } 171 | opts["to"] = recipient; 172 | opts["answer_url"] = answerUrl; 173 | this._sendVoiceMessage( 174 | { 175 | host: this.options.restHost || "rest.nexmo.com", 176 | path: "/call/json" 177 | }, 178 | opts, 179 | callback 180 | ); 181 | } 182 | } 183 | } 184 | 185 | export default Voice; 186 | -------------------------------------------------------------------------------- /test/Account-test.js: -------------------------------------------------------------------------------- 1 | import Account from "../lib/Account"; 2 | import { expect, sinon, TestUtils } from "./NexmoTestUtils"; 3 | 4 | describe("Account", function() { 5 | beforeEach(function() { 6 | this.httpClientStub = TestUtils.getHttpClient(); 7 | sinon.stub(this.httpClientStub, "request"); 8 | this.account = new Account(TestUtils.getCredentials(), { 9 | rest: this.httpClientStub, 10 | api: this.httpClientStub 11 | }); 12 | }); 13 | 14 | describe("checkBalance", function() { 15 | it("should call the correct endpoint", function() { 16 | return expect(this.account) 17 | .method("checkBalance") 18 | .to.get.url("/account/get-balance"); 19 | }); 20 | }); 21 | 22 | describe("updatePassword", function() { 23 | it("should call the correct endpoint", function() { 24 | return expect(this.account) 25 | .method("updatePassword") 26 | .withParams("example_password") 27 | .to.post.to.url("/account/settings?newSecret=example_password"); 28 | }); 29 | }); 30 | 31 | describe("updateSMSCallback", function() { 32 | it("should call the correct endpoint", function() { 33 | return expect(this.account) 34 | .method("updateSMSCallback") 35 | .withParams("http://example.com/sms_callback") 36 | .to.post.to.url( 37 | "/account/settings?moCallBackUrl=http%3A%2F%2Fexample.com%2Fsms_callback" 38 | ); 39 | }); 40 | }); 41 | 42 | describe("updateDeliveryReceiptCallback", function() { 43 | it("should call the correct endpoint", function() { 44 | return expect(this.account) 45 | .method("updateDeliveryReceiptCallback") 46 | .withParams("http://example.com/dr_callback") 47 | .to.post.to.url( 48 | "/account/settings?drCallBackUrl=http%3A%2F%2Fexample.com%2Fdr_callback" 49 | ); 50 | }); 51 | }); 52 | 53 | describe("topUp", function() { 54 | it("should call the correct endpoint", function() { 55 | return expect(this.account) 56 | .method("topUp") 57 | .withParams("ABC123") 58 | .to.post.to.url("/account/top-up?trx=ABC123"); 59 | }); 60 | 61 | it("returns data on a successful request", function(done) { 62 | const mockData = { 63 | // This is not accurate response as there are no examples in the docs 64 | // This test just shows that a successful response is passed through as expected 65 | success: true 66 | }; 67 | 68 | this.httpClientStub.request.yields(null, mockData); 69 | this.account.topUp("trx-123", (err, data) => { 70 | expect(err).to.eql(null); 71 | expect(data).to.eql(mockData); 72 | done(); 73 | }); 74 | }); 75 | 76 | it("returns an error on a failed request", function(done) { 77 | const mockData = { 78 | // This is not accurate response as there are no examples in the docs 79 | // This test just shows that a successful response is passed through as expected 80 | success: false 81 | }; 82 | 83 | this.httpClientStub.request.yields(mockData, null); 84 | this.account.topUp("trx-123", (err, data) => { 85 | expect(err).to.eql(mockData); 86 | expect(data).to.eql(null); 87 | done(); 88 | }); 89 | }); 90 | }); 91 | 92 | describe("listSecrets", function() { 93 | it("should call the correct endpoint", function() { 94 | return expect(this.account) 95 | .method("listSecrets") 96 | .withParams("ABC123") 97 | .to.get.url("/accounts/ABC123/secrets"); 98 | }); 99 | }); 100 | 101 | describe("getSecret", function() { 102 | it("should call the correct endpoint", function() { 103 | return expect(this.account) 104 | .method("getSecret") 105 | .withParams("ABC123", "123") 106 | .to.get.url("/accounts/ABC123/secrets/123"); 107 | }); 108 | }); 109 | 110 | describe("createSecret", function() { 111 | it("should call the correct endpoint", function() { 112 | return expect(this.account) 113 | .method("createSecret") 114 | .withParams("ABC123", "123") 115 | .to.post.withJsonBody({ secret: "123" }) 116 | .to.url("/accounts/ABC123/secrets/"); 117 | }); 118 | }); 119 | 120 | describe("deleteSecret", function() { 121 | it("should call the correct endpoint", function() { 122 | return expect(this.account) 123 | .method("deleteSecret") 124 | .withParams("ABC123", "123") 125 | .to.delete.url("/accounts/ABC123/secrets/123"); 126 | }); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/ConsoleLogger-test.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from "chai"; 2 | import sinon from "sinon"; 3 | import sinonChai from "sinon-chai"; 4 | import ConsoleLogger from "../lib/ConsoleLogger"; 5 | 6 | chai.use(sinonChai); 7 | 8 | describe("ConsoleLogger", () => { 9 | describe(".log", () => { 10 | it("should log `info` using `console.log`", () => { 11 | var fakeConsole = { 12 | log: () => {} 13 | }; 14 | var stub = sinon.stub(fakeConsole); 15 | 16 | var logger = new ConsoleLogger(stub); 17 | logger.info("something"); 18 | 19 | expect(stub.log).to.have.been.calledWith("info:", "something"); 20 | }); 21 | 22 | it("should log `error` using `console.error`", () => { 23 | var fakeConsole = { 24 | error: () => {} 25 | }; 26 | var stub = sinon.stub(fakeConsole); 27 | 28 | var logger = new ConsoleLogger(stub); 29 | logger.error("an error"); 30 | 31 | expect(stub.error).to.have.been.calledWith("error:", "an error"); 32 | }); 33 | 34 | it("should log `warn` using `console.log`", () => { 35 | var fakeConsole = { 36 | log: () => {} 37 | }; 38 | var stub = sinon.stub(fakeConsole); 39 | 40 | var logger = new ConsoleLogger(stub); 41 | logger.warn("a warning"); 42 | 43 | expect(stub.log).to.have.been.calledWith("warn:", "a warning"); 44 | }); 45 | 46 | it("should log levels using `console.log`", () => { 47 | var fakeConsole = { 48 | log: () => {} 49 | }; 50 | var stub = sinon.stub(fakeConsole); 51 | 52 | var logger = new ConsoleLogger(stub); 53 | logger.log("silly", "something silly"); 54 | 55 | expect(stub.log).to.have.been.calledWith("silly:", "something silly"); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/Conversion-test.js: -------------------------------------------------------------------------------- 1 | import Conversion from "../lib/Conversion"; 2 | import Credentials from "../lib/Credentials"; 3 | import HttpClient from "../lib/HttpClient"; 4 | import NullLogger from "../lib/ConsoleLogger"; 5 | 6 | import ResourceTestHelper from "./ResourceTestHelper"; 7 | import sinon from "sinon"; 8 | import chai, { expect } from "chai"; 9 | import sinonChai from "sinon-chai"; 10 | chai.use(sinonChai); 11 | 12 | describe("Conversion", function() { 13 | beforeEach(function() { 14 | var creds = Credentials.parse({ 15 | apiKey: "myKey", 16 | apiSecret: "mySecret" 17 | }); 18 | 19 | this.httpClientStub = new HttpClient( 20 | { 21 | logger: new NullLogger() 22 | }, 23 | creds 24 | ); 25 | 26 | sinon.stub(this.httpClientStub, "request"); 27 | 28 | var options = { 29 | api: this.httpClientStub 30 | }; 31 | 32 | this.conversion = new Conversion(creds, options); 33 | }); 34 | 35 | describe("#submit", function() { 36 | it("should call the correct endpoint", function(done) { 37 | this.httpClientStub.request.yields(null, {}); 38 | 39 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch({ 40 | path: 41 | "/conversions/foo?message-id=1234&delivered=1×tamp=1513254618" 42 | }); 43 | 44 | this.conversion.submit( 45 | "foo", 46 | "1234", 47 | 1, 48 | 1513254618, 49 | function(err, data) { 50 | expect(this.httpClientStub.request).to.have.been.calledWith( 51 | sinon.match(expectedRequestArgs) 52 | ); 53 | 54 | done(); 55 | }.bind(this) 56 | ); 57 | }); 58 | 59 | it("returns a friendly error when not enabled", function(done) { 60 | const mockError = { 61 | status: 402 62 | }; 63 | 64 | this.httpClientStub.request.yields(mockError, null); 65 | this.conversion.sms("1234", 1, 1234567890, function(err, data) { 66 | expect(err._INFO_).to.eql( 67 | "This endpoint may need activating on your account. Please email support@nexmo.com for more information" 68 | ); 69 | expect(data).to.eql(null); 70 | done(); 71 | }); 72 | }); 73 | }); 74 | 75 | describe("#voice", function() { 76 | it("calls the correct endpoint for voice", function() { 77 | const submitStub = sinon.stub(this.conversion, "submit"); 78 | this.conversion.voice("1234", 1, 1513254618); 79 | expect(submitStub).to.have.been.calledWith( 80 | "voice", 81 | "1234", 82 | 1, 83 | 1513254618 84 | ); 85 | submitStub.restore(); 86 | }); 87 | }); 88 | 89 | describe("#sms", function() { 90 | it("calls the correct endpoint for sms", function() { 91 | const submitStub = sinon.stub(this.conversion, "submit"); 92 | this.conversion.sms("1234", 1, 1513254618); 93 | expect(submitStub).to.have.been.calledWith("sms", "1234", 1, 1513254618); 94 | submitStub.restore(); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/Credentials-test.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from "chai"; 2 | import path from "path"; 3 | import sinon from "sinon"; 4 | import sinonChai from "sinon-chai"; 5 | import fs from "fs"; 6 | 7 | import JwtGenerator from "../lib/JwtGenerator"; 8 | import HashGenerator from "../lib/HashGenerator"; 9 | 10 | import Credentials from "../lib/Credentials"; 11 | 12 | chai.use(sinonChai); 13 | 14 | describe("Credentials Object", function() { 15 | it("should be possible to construct a Credential object", function() { 16 | var cred = Credentials.parse("KEY", "SECRET"); 17 | 18 | expect(cred).to.be.an.instanceof(Credentials); 19 | }); 20 | 21 | it("should parse object literal into a Credential object", function() { 22 | var key = "KEY"; 23 | var secret = "SECRET"; 24 | var appId = "app-id"; 25 | var privateKey = path.join(__dirname, "private-test.key"); 26 | var obj = { 27 | apiKey: key, 28 | apiSecret: secret, 29 | applicationId: appId, 30 | privateKey: privateKey 31 | }; 32 | var parsed = Credentials.parse(obj); 33 | 34 | expect(parsed).to.be.an.instanceof(Credentials); 35 | expect(parsed.apiKey).to.be.equal(key); 36 | expect(parsed.apiSecret).to.be.equal(secret); 37 | expect(parsed.applicationId).to.be.equal(appId); 38 | expect(parsed.privateKey).to.be.ok; 39 | }); 40 | 41 | it("should not parse Credential object", function() { 42 | var cred = new Credentials("KEY", "SECRET"); 43 | var parsed = Credentials.parse(cred); 44 | 45 | expect(parsed.signatureMethod).to.equal(undefined); 46 | }); 47 | 48 | it("should throw an error when a privateKey is provided and the file does not exist", function() { 49 | var create = function() { 50 | return new Credentials("KEY", "SECRET", "./no-key-here.key"); 51 | }; 52 | expect(create).to.throw(Error); 53 | }); 54 | 55 | it("should read a private key from a file into a Buffer accessible via Credentials.privateKey", function() { 56 | var cred = new Credentials( 57 | "KEY", 58 | "SECRET", 59 | path.join(__dirname, "private-test.key") 60 | ); 61 | expect(cred.privateKey).to.be.an.instanceof(Buffer); 62 | }); 63 | 64 | it("should turn a private key string into a Buffer accessible via Credentials.privateKey", function() { 65 | var key = fs.readFileSync(path.join(__dirname, "private-test.key")); 66 | var cred = new Credentials("KEY", "SECRET", key); 67 | expect(cred.privateKey).to.be.an.instanceof(Buffer); 68 | }); 69 | 70 | it("should support passing a privateKey of type string", function() { 71 | var key = `-----BEGIN PRIVATE KEY----- 72 | blah blah blah 73 | -----END PRIVATE KEY-----`; 74 | var cred = new Credentials("KEY", "SECRET", key); 75 | expect(cred.privateKey).to.be.an.instanceof(Buffer); 76 | }); 77 | 78 | it("should allow an applicationId to be provided upon construction", function() { 79 | var appId = "some_app_id"; 80 | var cred = new Credentials( 81 | "KEY", 82 | "SECRET", 83 | path.join(__dirname, "private-test.key"), 84 | appId 85 | ); 86 | expect(cred.applicationId).to.equal(appId); 87 | }); 88 | 89 | it("should allow a JWT to be generated using supplied application ID", function() { 90 | var stub = sinon.createStubInstance(JwtGenerator); 91 | 92 | var cred = new Credentials( 93 | "KEY", 94 | "SECRET", 95 | path.join(__dirname, "private-test.key"), 96 | "app-id" 97 | ); 98 | cred._setJwtGenerator(stub); 99 | 100 | cred.generateJwt(); 101 | 102 | expect(stub.generate).to.be.calledWith(cred.privateKey, { 103 | application_id: cred.applicationId 104 | }); 105 | }); 106 | 107 | it("should allow a JWT to be generated using an alternative application ID", function() { 108 | var stub = sinon.createStubInstance(JwtGenerator); 109 | 110 | var cred = new Credentials( 111 | "KEY", 112 | "SECRET", 113 | path.join(__dirname, "private-test.key"), 114 | "app-id" 115 | ); 116 | cred._setJwtGenerator(stub); 117 | 118 | var altAppId = "another-app-id"; 119 | cred.generateJwt(altAppId); 120 | 121 | expect(stub.generate).to.be.calledWith(cred.privateKey, { 122 | application_id: altAppId 123 | }); 124 | }); 125 | 126 | it("should allow a JWT to be generated using an alternative private key", function() { 127 | var stub = sinon.createStubInstance(JwtGenerator); 128 | 129 | var cred = new Credentials( 130 | "KEY", 131 | "SECRET", 132 | path.join(__dirname, "private-test.key"), 133 | "app-id" 134 | ); 135 | cred._setJwtGenerator(stub); 136 | 137 | var altAppId = "another-app-id"; 138 | cred.generateJwt(altAppId, "ALTERNATIVE_KEY"); 139 | 140 | expect(stub.generate).to.be.calledWith("ALTERNATIVE_KEY", { 141 | application_id: altAppId 142 | }); 143 | }); 144 | 145 | it("should allow a hash to be generated using supplied signature secret and method", function() { 146 | var stub = sinon.createStubInstance(HashGenerator); 147 | 148 | var cred = new Credentials( 149 | "KEY", 150 | "SECRET", 151 | undefined, 152 | undefined, 153 | "secret", 154 | "md5hash" 155 | ); 156 | 157 | cred._setHashGenerator(stub); 158 | 159 | cred.generateSignature({}); 160 | 161 | expect(stub.generate).to.be.calledWith( 162 | cred.signatureMethod, 163 | cred.signatureSecret, 164 | {} 165 | ); 166 | }); 167 | 168 | it("should allow a hash to be generated using alternate signature secret and method", function() { 169 | var stub = sinon.createStubInstance(HashGenerator); 170 | 171 | var cred = new Credentials( 172 | "KEY", 173 | "SECRET", 174 | undefined, 175 | undefined, 176 | "secret", 177 | "md5hash" 178 | ); 179 | 180 | cred._setHashGenerator(stub); 181 | 182 | cred.generateSignature({}, "md5", "secrit"); 183 | 184 | expect(stub.generate).to.be.calledWith("secrit", "md5", {}); 185 | }); 186 | }); 187 | -------------------------------------------------------------------------------- /test/DtmfResource-test.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from "chai"; 2 | 3 | import path from "path"; 4 | import sinon from "sinon"; 5 | import sinonChai from "sinon-chai"; 6 | 7 | import ResourceTestHelper from "./ResourceTestHelper"; 8 | 9 | import DtmfResource from "../lib/DtmfResource"; 10 | import HttpClient from "../lib/HttpClient"; 11 | import Credentials from "../lib/Credentials"; 12 | 13 | chai.use(sinonChai); 14 | 15 | var creds = Credentials.parse({ 16 | applicationId: "some-id", 17 | privateKey: path.join(__dirname, "private-test.key") 18 | }); 19 | var emptyCallback = () => {}; 20 | 21 | describe("DtmfResource", () => { 22 | var httpClientStub = null; 23 | var dtmf = null; 24 | 25 | beforeEach(() => { 26 | httpClientStub = sinon.createStubInstance(HttpClient); 27 | var options = { 28 | httpClient: httpClientStub 29 | }; 30 | dtmf = new DtmfResource(creds, options); 31 | }); 32 | 33 | it("should be able to send DTMF to a call", () => { 34 | const callId = "2342342-lkjhlkjh-32423"; 35 | var params = { 36 | digits: [1, 2, 3, 4] 37 | }; 38 | dtmf.send(callId, params, emptyCallback); 39 | 40 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch(params, { 41 | path: DtmfResource.PATH.replace("{call_uuid}", callId), 42 | method: "PUT", 43 | headers: { 44 | "Content-Type": "application/json", 45 | "Content-Length": 20 46 | } 47 | }); 48 | expect(httpClientStub.request).to.have.been.calledWith( 49 | sinon.match(expectedRequestArgs), 50 | emptyCallback 51 | ); 52 | }); 53 | 54 | it("should support host override", () => { 55 | let httpClientStub = sinon.createStubInstance(HttpClient); 56 | let options = { 57 | httpClient: httpClientStub, 58 | apiHost: "api.example.com" 59 | }; 60 | let dtmf = new DtmfResource(creds, options); 61 | const callId = "2342342-lkjhlkjh-32423"; 62 | dtmf.send(callId, {}, emptyCallback); 63 | 64 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch( 65 | {}, 66 | { 67 | path: DtmfResource.PATH.replace("{call_uuid}", callId), 68 | method: "PUT", 69 | host: "api.example.com", 70 | headers: { 71 | "Content-Type": "application/json" 72 | } 73 | } 74 | ); 75 | expect(httpClientStub.request).to.have.been.calledWith( 76 | sinon.match(expectedRequestArgs), 77 | emptyCallback 78 | ); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/FilesResource-test.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from "chai"; 2 | import path from "path"; 3 | import sinon from "sinon"; 4 | import sinonChai from "sinon-chai"; 5 | 6 | import ResourceTestHelper from "./ResourceTestHelper"; 7 | 8 | import FilesResource from "../lib/FilesResource"; 9 | import HttpClient from "../lib/HttpClient"; 10 | import Credentials from "../lib/Credentials"; 11 | 12 | chai.use(sinonChai); 13 | 14 | var creds = Credentials.parse({ 15 | applicationId: "some-id", 16 | privateKey: path.join(__dirname, "private-test.key") 17 | }); 18 | var emptyCallback = () => {}; 19 | 20 | describe("FileResource", () => { 21 | var httpClientStub = null; 22 | var files = null; 23 | 24 | beforeEach(() => { 25 | httpClientStub = sinon.createStubInstance(HttpClient); 26 | var options = { 27 | httpClient: httpClientStub 28 | }; 29 | files = new FilesResource(creds, options); 30 | }); 31 | 32 | it("should support host override", () => { 33 | let httpClientStub = sinon.createStubInstance(HttpClient); 34 | let options = { 35 | httpClient: httpClientStub, 36 | apiHost: "api.example.com" 37 | }; 38 | let files = new FilesResource(creds, options); 39 | const fileId = "2342342-lkjhlkjh-32423"; 40 | files.get(fileId, emptyCallback); 41 | 42 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch(null, { 43 | method: "GET", 44 | body: undefined, 45 | host: "api.example.com", 46 | path: `${FilesResource.PATH}/${fileId}`, 47 | headers: { 48 | "Content-Type": "application/octet-stream", 49 | Authorization: "Bearer " 50 | } 51 | }); 52 | 53 | expect(httpClientStub.request).to.have.been.calledWith( 54 | sinon.match(expectedRequestArgs), 55 | emptyCallback 56 | ); 57 | }); 58 | 59 | it("should get a single file using a file ID", () => { 60 | const fileId = "2342342-lkjhlkjh-32423"; 61 | files.get(fileId, emptyCallback); 62 | 63 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch(null, { 64 | method: "GET", 65 | body: undefined, 66 | path: `${FilesResource.PATH}/${fileId}`, 67 | headers: { 68 | "Content-Type": "application/octet-stream", 69 | Authorization: "Bearer " 70 | } 71 | }); 72 | 73 | expect(httpClientStub.request).to.have.been.calledWith( 74 | sinon.match(expectedRequestArgs), 75 | emptyCallback 76 | ); 77 | }); 78 | 79 | it("should get a single file using a file URL", () => { 80 | const fileId = "2342342-lkjhlkjh-32423"; 81 | const fileUrl = `https://rest.nexmo.com/api/v1/files/${fileId}`; 82 | files.get(fileUrl, emptyCallback); 83 | 84 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch(null, { 85 | method: "GET", 86 | body: undefined, 87 | path: `${FilesResource.PATH}/${fileId}`, 88 | headers: { 89 | "Content-Type": "application/octet-stream", 90 | Authorization: "Bearer " 91 | } 92 | }); 93 | 94 | expect(httpClientStub.request).to.have.been.calledWith( 95 | sinon.match(expectedRequestArgs), 96 | emptyCallback 97 | ); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/HashGenerator-test.js: -------------------------------------------------------------------------------- 1 | import HashGenerator from "../src/HashGenerator"; 2 | import expect from "expect.js"; 3 | 4 | describe("HashGenerator Object", function() { 5 | describe(".generate", function() { 6 | it("should throw an exception if method is not md5hash, md5, sha1, sha256, or sha512", function() { 7 | var generator = new HashGenerator(); 8 | var generate = function() { 9 | generator.generate("random"); 10 | }; 11 | expect(generate).to.throwError(); 12 | }); 13 | 14 | it("should strip sig if present", function() { 15 | var generator = new HashGenerator(); 16 | var hash = generator.generate("md5hash", "secret", { sig: "signature" }); 17 | 18 | expect(hash).to.be("5ebe2294ecd0e0f08eab7690d2a6ee69"); 19 | }); 20 | 21 | it("should generate a md5hash hash", function() { 22 | var generator = new HashGenerator(); 23 | var hash = generator.generate("md5hash", "secret", {}); 24 | 25 | expect(hash).to.be("5ebe2294ecd0e0f08eab7690d2a6ee69"); 26 | }); 27 | 28 | it("should generate a hash from params", function() { 29 | var generator = new HashGenerator(); 30 | var hash = generator.generate("md5hash", "secret", { from: "NEXMO" }); 31 | 32 | expect(hash).to.be("2cdd20a2a0f7270545a98b3ccb87ba51"); 33 | }); 34 | 35 | it("should generate a md5 hash", function() { 36 | var generator = new HashGenerator(); 37 | var hash = generator.generate("md5", "secret", {}); 38 | 39 | expect(hash).to.be("5c8db03f04cec0f43bcb060023914190"); 40 | }); 41 | 42 | it("should generate a sha1 hash", function() { 43 | var generator = new HashGenerator(); 44 | var hash = generator.generate("sha1", "secret", {}); 45 | 46 | expect(hash).to.be("25af6174a0fcecc4d346680a72b7ce644b9a88e8"); 47 | }); 48 | 49 | it("should generate a sha256 hash", function() { 50 | var generator = new HashGenerator(); 51 | var hash = generator.generate("sha256", "secret", {}); 52 | 53 | expect(hash).to.be( 54 | "f9e66e179b6747ae54108f82f8ade8b3c25d76fd30afde6c395822c530196169" 55 | ); 56 | }); 57 | 58 | it("should generate a sha512 hash", function() { 59 | var generator = new HashGenerator(); 60 | var hash = generator.generate("sha512", "secret", {}); 61 | 62 | expect(hash).to.be( 63 | "b0e9650c5faf9cd8ae02276671545424104589b3656731ec193b25d01b07561c27637c2d4d68389d6cf5007a8632c26ec89ba80a01c77a6cdd389ec28db43901" 64 | ); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/JwtGenerator-test.js: -------------------------------------------------------------------------------- 1 | import JwtGenerator from "../src/JwtGenerator"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import expect from "expect.js"; 5 | import jwt from "jsonwebtoken"; 6 | 7 | describe("JwtGenerator Object", function() { 8 | describe(".generate", function() { 9 | it("should throw an exception if the cert is not a Buffer", function() { 10 | var generator = new JwtGenerator(); 11 | var generate = function() { 12 | generator.generate("blah blah"); 13 | }; 14 | expect(generate).to.throwError(); 15 | }); 16 | 17 | it("should throw an exception if the claims is not a Object", function() { 18 | var generator = new JwtGenerator(); 19 | var generate = function() { 20 | generator.generate("blah blah", "application_id"); 21 | }; 22 | expect(generate).to.throwError(); 23 | }); 24 | 25 | it("should generate a JWT", function() { 26 | var testPrivateKey = fs.readFileSync( 27 | path.join(__dirname, "private-test.key") 28 | ); 29 | 30 | var generator = new JwtGenerator(); 31 | var token = generator.generate(testPrivateKey, { 32 | application_id: "app-id", 33 | iat: new Date(2016, 9, 5).getTime() / 1000 34 | }); 35 | 36 | expect(token).to.be.a("string"); 37 | }); 38 | 39 | it("should add jti and iat claims by default", function() { 40 | var testPrivateKey = fs.readFileSync( 41 | path.join(__dirname, "private-test.key") 42 | ); 43 | var testPublicKey = fs.readFileSync( 44 | path.join(__dirname, "public-test.key") 45 | ); 46 | 47 | var generator = new JwtGenerator(); 48 | var token = generator.generate(testPrivateKey); 49 | 50 | var decoded = jwt.verify(token, testPublicKey, { algorithms: ["RS256"] }); 51 | 52 | expect(decoded.jti).to.be.ok(); 53 | expect(decoded.iat).to.be.ok(); 54 | }); 55 | 56 | it("should be possible to add additional claims", function() { 57 | var testPrivateKey = fs.readFileSync( 58 | path.join(__dirname, "private-test.key") 59 | ); 60 | var testPublicKey = fs.readFileSync( 61 | path.join(__dirname, "public-test.key") 62 | ); 63 | 64 | var generator = new JwtGenerator(); 65 | var appId = "app-id"; 66 | var randomValue = Math.random(); 67 | var token = generator.generate(testPrivateKey, { 68 | application_id: appId, 69 | random: randomValue 70 | }); 71 | 72 | var decoded = jwt.verify(token, testPublicKey, { algorithms: ["RS256"] }); 73 | 74 | expect(decoded.application_id).to.be(appId); 75 | expect(decoded.random).to.be(randomValue); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/Media-test.js: -------------------------------------------------------------------------------- 1 | import Media from "../lib/Media"; 2 | import os from "os"; 3 | import { expect, sinon, TestUtils } from "./NexmoTestUtils"; 4 | 5 | describe("Media", function() { 6 | beforeEach(function() { 7 | this.httpClientStub = TestUtils.getHttpClient(); 8 | sinon.stub(this.httpClientStub, "request"); 9 | sinon.stub(this.httpClientStub.requestLib, "post"); 10 | this.media = new Media(TestUtils.getCredentials(), { 11 | api: this.httpClientStub 12 | }); 13 | }); 14 | 15 | afterEach(function() { 16 | this.httpClientStub.request.restore(); 17 | this.httpClientStub.requestLib.post.restore(); 18 | }); 19 | 20 | describe("#search", function() { 21 | it("should default to no parameters", function() { 22 | return expect(this.media) 23 | .method("search") 24 | .to.get.url(Media.PATH); 25 | }); 26 | 27 | it("should pass through supplied parameters", function() { 28 | return expect(this.media) 29 | .method("search") 30 | .withParams({ order: "ascending", page_size: 11 }) 31 | .to.get.url(`${Media.PATH}?order=ascending&page_size=11`); 32 | }); 33 | }); 34 | 35 | describe("#download", function() { 36 | it("should call the correct URL", function() { 37 | return expect(this.media) 38 | .method("download") 39 | .withParams("ABC123") 40 | .to.get.url(`${Media.PATH}/ABC123`); 41 | }); 42 | }); 43 | 44 | describe("#get", function() { 45 | it("should call the correct URL", function() { 46 | return expect(this.media) 47 | .method("get") 48 | .withParams("ABC123") 49 | .to.get.url(`${Media.PATH}/ABC123/info`); 50 | }); 51 | }); 52 | 53 | describe("#delete", function() { 54 | it("should call the correct URL", function() { 55 | return expect(this.media) 56 | .method("delete") 57 | .withParams("ABC123") 58 | .to.delete.url(`${Media.PATH}/ABC123`); 59 | }); 60 | }); 61 | 62 | describe("#upload", function() { 63 | // @TODO Add assertions for POST body 64 | // @TODO Add mocks for request 65 | it("should call the correct URL (file provided)", function() { 66 | const file = os.type() === "Windows_NT" ? "\\\\.\\NUL" : "/dev/null"; 67 | return expect(this.media) 68 | .method("upload") 69 | .withParams({ file }) 70 | .to.postFile.to.url("/v3/media"); 71 | }); 72 | 73 | it("should call the correct URL (url provided)", function() { 74 | return expect(this.media) 75 | .method("upload") 76 | .withParams({ url: "http://example.com" }) 77 | .to.postFile.to.url("/v3/media"); 78 | }); 79 | }); 80 | 81 | describe("#update", function() { 82 | it("should call the correct URL", function() { 83 | return expect(this.media) 84 | .method("update") 85 | .withParams("ABC123", { public_item: true }) 86 | .to.put.to.url("/v3/media/ABC123/info"); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/NexmoTestUtils.js: -------------------------------------------------------------------------------- 1 | import Credentials from "../lib/Credentials"; 2 | import HttpClient from "../lib/HttpClient"; 3 | import NullLogger from "../lib/ConsoleLogger.js"; 4 | 5 | import sinon from "sinon"; 6 | import chai, { expect } from "chai"; 7 | import sinonChai from "sinon-chai"; 8 | import nexmoChai from "./NexmoChai"; 9 | chai.use(sinonChai); 10 | chai.use(nexmoChai); 11 | 12 | const TestUtils = { 13 | getCredentials: function() { 14 | var creds = Credentials.parse({ 15 | apiKey: "myKey", 16 | apiSecret: "mySecret" 17 | }); 18 | 19 | // Overwrite JWT generation for tests 20 | creds.generateJwt = function() { 21 | return "ThisIsAJWT"; 22 | }; 23 | 24 | return creds; 25 | }, 26 | getHttpClient: function() { 27 | const httpClient = new HttpClient( 28 | { 29 | logger: new NullLogger() 30 | }, 31 | this.getCredentials() 32 | ); 33 | 34 | return httpClient; 35 | } 36 | }; 37 | 38 | export { TestUtils, expect, sinon }; 39 | -------------------------------------------------------------------------------- /test/Number-pricing-test.js: -------------------------------------------------------------------------------- 1 | import Number from "../lib/Number"; 2 | 3 | import { expect, sinon, TestUtils } from "./NexmoTestUtils"; 4 | 5 | describe("Number _pricing", function() { 6 | beforeEach(function() { 7 | this.sandbox = sinon.sandbox.create(); 8 | this.httpClientStub = TestUtils.getHttpClient(); 9 | this.sandbox.stub(this.httpClientStub, "request"); 10 | this.number = new Number(TestUtils.getCredentials(), { 11 | rest: this.httpClientStub 12 | }); 13 | }); 14 | 15 | afterEach(function() { 16 | this.sandbox.restore(); 17 | }); 18 | 19 | it("should call the correct endpoint for getPricing", function() { 20 | return expect(this.number) 21 | .method("getPricing") 22 | .withParams("sms", "GB") 23 | .to.get.url("/account/get-pricing/outbound/sms?country=GB"); 24 | }); 25 | 26 | it("should call the correct endpoint for getPhonePricing", function() { 27 | return expect(this.number) 28 | .method("getPhonePricing") 29 | .withParams("sms", "442038659460") 30 | .to.get.url("/account/get-phone-pricing/outbound/sms?phone=442038659460"); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/Number-test.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from "chai"; 2 | import path from "path"; 3 | import sinon from "sinon"; 4 | import sinonChai from "sinon-chai"; 5 | 6 | import Number from "../lib/Number"; 7 | import HttpClient from "../lib/HttpClient"; 8 | import Credentials from "../lib/Credentials"; 9 | 10 | import ResourceTestHelper from "./ResourceTestHelper"; 11 | 12 | chai.use(sinonChai); 13 | 14 | var creds = Credentials.parse({ 15 | apiKey: "some-key", 16 | apiSecret: "some-secret" 17 | }); 18 | var emptyCallback = () => {}; 19 | 20 | describe("Number", () => { 21 | var httpClientStub = null; 22 | var number = null; 23 | 24 | beforeEach(() => { 25 | httpClientStub = sinon.createStubInstance(HttpClient); 26 | var options = { 27 | httpClient: httpClientStub, 28 | logger: { 29 | info: () => {} 30 | } 31 | }; 32 | number = new Number(creds, options); 33 | }); 34 | 35 | it("should throw if there are no parameters when getting numbers", () => { 36 | try { 37 | number.get(); 38 | } catch (e) { 39 | expect(e.toString()).to.include( 40 | "Options parameter should be a dictionary. Check the docs for valid properties for options" 41 | ); 42 | } 43 | }); 44 | 45 | it("should throw if there is no country code when searching numbers", () => { 46 | try { 47 | number.search(); 48 | } catch (e) { 49 | expect(e.toString()).to.include("Invalid Country Code"); 50 | } 51 | }); 52 | 53 | it("should throw if there is no country code when buying numbers", () => { 54 | try { 55 | number.buy(); 56 | } catch (e) { 57 | expect(e.toString()).to.include("Invalid Country Code"); 58 | } 59 | }); 60 | 61 | it("should throw if there is no country code when cancelling numbers", () => { 62 | try { 63 | number.cancel(); 64 | } catch (e) { 65 | expect(e.toString()).to.include("Invalid Country Code"); 66 | } 67 | }); 68 | 69 | it("should throw if there is no country code when updating numbers", () => { 70 | try { 71 | number.update(); 72 | } catch (e) { 73 | expect(e.toString()).to.include("Invalid Country Code"); 74 | } 75 | }); 76 | 77 | it("should throw if there is no MSISDN when updating numbers", () => { 78 | try { 79 | number.update("GB"); 80 | } catch (e) { 81 | expect(e.toString()).to.include("Invalid MSISDN passed"); 82 | } 83 | }); 84 | 85 | it("should throw if there is no MSISDN when buying numbers", () => { 86 | try { 87 | number.buy("GB"); 88 | } catch (e) { 89 | expect(e.toString()).to.include("Invalid MSISDN passed"); 90 | } 91 | }); 92 | 93 | it("should throw if there is no MSISDN when cancelling numbers", () => { 94 | try { 95 | number.cancel("GB"); 96 | } catch (e) { 97 | expect(e.toString()).to.include("Invalid MSISDN passed"); 98 | } 99 | }); 100 | 101 | it("should throw if the country code is not 2 letters when searching numbers", () => { 102 | try { 103 | number.search("G"); 104 | } catch (e) { 105 | expect(e.toString()).to.include("Invalid Country Code"); 106 | } 107 | }); 108 | 109 | it("should allow getting account numbers", () => { 110 | number.get({}, emptyCallback); 111 | 112 | expect(httpClientStub.request).to.have.been.calledWith( 113 | sinon.match({ 114 | path: "/account/numbers?api_key=some-key&api_secret=some-secret" 115 | }) 116 | ); 117 | }); 118 | 119 | it("should allow getting account numbers without options", () => { 120 | number.get(emptyCallback); 121 | 122 | expect(httpClientStub.request).to.have.been.calledWith( 123 | sinon.match({ 124 | path: "/account/numbers?api_key=some-key&api_secret=some-secret" 125 | }) 126 | ); 127 | }); 128 | it("should allow searching available numbers", () => { 129 | number.search("GB", emptyCallback); 130 | 131 | expect(httpClientStub.request).to.have.been.calledWith( 132 | sinon.match({ 133 | path: 134 | "/number/search?api_key=some-key&api_secret=some-secret&country=GB" 135 | }) 136 | ); 137 | }); 138 | 139 | it("should allow searching available numbers with a pattern", () => { 140 | number.search("GB", "222", emptyCallback); 141 | 142 | expect(httpClientStub.request).to.have.been.calledWith( 143 | sinon.match({ 144 | path: 145 | "/number/search?api_key=some-key&api_secret=some-secret&country=GB&pattern=222" 146 | }) 147 | ); 148 | }); 149 | 150 | it("should allow searching available numbers with options", () => { 151 | number.search("GB", { pattern: "222" }, emptyCallback); 152 | 153 | expect(httpClientStub.request).to.have.been.calledWith( 154 | sinon.match({ 155 | path: 156 | "/number/search?api_key=some-key&api_secret=some-secret&country=GB&pattern=222" 157 | }) 158 | ); 159 | }); 160 | 161 | it("should allow buying available numbers", () => { 162 | number.buy("GB", "1234", emptyCallback); 163 | 164 | expect(httpClientStub.request).to.have.been.calledWith( 165 | sinon.match({ 166 | path: 167 | "/number/buy?country=GB&msisdn=1234&api_key=some-key&api_secret=some-secret" 168 | }) 169 | ); 170 | }); 171 | 172 | it("should allow buying with a target api key", () => { 173 | number.buy("GB", "1234", "5678", emptyCallback); 174 | 175 | expect(httpClientStub.request).to.have.been.calledWith( 176 | sinon.match({ 177 | path: 178 | "/number/buy?country=GB&msisdn=1234&api_key=some-key&api_secret=some-secret&target_api_key=5678" 179 | }) 180 | ); 181 | }); 182 | 183 | it("should allow cancelling available numbers", () => { 184 | number.cancel("GB", "1234", emptyCallback); 185 | 186 | expect(httpClientStub.request).to.have.been.calledWith( 187 | sinon.match({ 188 | path: 189 | "/number/cancel?country=GB&msisdn=1234&api_key=some-key&api_secret=some-secret" 190 | }) 191 | ); 192 | }); 193 | 194 | it("should allow cancelling available numbers with a target api key", () => { 195 | number.cancel("GB", "1234", "5678", emptyCallback); 196 | 197 | expect(httpClientStub.request).to.have.been.calledWith( 198 | sinon.match({ 199 | path: 200 | "/number/cancel?country=GB&msisdn=1234&api_key=some-key&api_secret=some-secret&target_api_key=5678" 201 | }) 202 | ); 203 | }); 204 | 205 | it("should allow updating available numbers", () => { 206 | number.update("GB", "1234", {}, emptyCallback); 207 | 208 | expect(httpClientStub.request).to.have.been.calledWith( 209 | sinon.match({ 210 | path: 211 | "/number/update?country=GB&msisdn=1234&api_key=some-key&api_secret=some-secret" 212 | }) 213 | ); 214 | }); 215 | }); 216 | -------------------------------------------------------------------------------- /test/NumberInsight-test.js: -------------------------------------------------------------------------------- 1 | import NumberInsight from "../lib/NumberInsight"; 2 | import chai, { expect } from "chai"; 3 | import path from "path"; 4 | import sinon from "sinon"; 5 | import sinonChai from "sinon-chai"; 6 | 7 | import HttpClient from "../lib/HttpClient"; 8 | import Credentials from "../lib/Credentials"; 9 | 10 | import ResourceTestHelper from "./ResourceTestHelper"; 11 | 12 | chai.use(sinonChai); 13 | 14 | var creds = Credentials.parse({ 15 | apiKey: "some-key", 16 | apiSecret: "some-secret" 17 | }); 18 | var emptyCallback = () => {}; 19 | 20 | describe("NumberInsight", () => { 21 | var httpClientStub = null; 22 | var numberInsight = null; 23 | 24 | beforeEach(() => { 25 | httpClientStub = sinon.createStubInstance(HttpClient); 26 | var options = { 27 | httpClient: httpClientStub, 28 | logger: { 29 | info: () => {} 30 | } 31 | }; 32 | numberInsight = new NumberInsight(creds, options); 33 | }); 34 | 35 | it("should throw if there is no number for basic numberInsights", () => { 36 | try { 37 | numberInsight.get({}); 38 | } catch (e) { 39 | expect(e.toString()).to.include("Missing Mandatory field - number"); 40 | } 41 | }); 42 | 43 | it("should throw if there is no params for basic numberInsights", () => { 44 | try { 45 | numberInsight.get(""); 46 | } catch (e) { 47 | expect(e.toString()).to.include( 48 | "Number can contain digits and may include any or all of the following: white space, -,+, (, )." 49 | ); 50 | } 51 | }); 52 | 53 | it("should throw if there is no number format for basic numberInsights", () => { 54 | try { 55 | numberInsight.get({ level: "basic", number: "test" }); 56 | } catch (e) { 57 | expect(e.toString()).to.include( 58 | "Number can contain digits and may include any or all of the following: white space, -,+, (, )." 59 | ); 60 | } 61 | }); 62 | 63 | it("should throw if there is no number for advanced numberInsights", () => { 64 | try { 65 | numberInsight.get({ level: "advanced" }); 66 | } catch (e) { 67 | expect(e.toString()).to.include( 68 | "Missing Mandatory fields (number and/or callback url)" 69 | ); 70 | } 71 | }); 72 | 73 | it("should throw if there is no callback for advanced numberInsights", () => { 74 | try { 75 | numberInsight.get({ level: "advanced", number: "123456789" }); 76 | } catch (e) { 77 | expect(e.toString()).to.include( 78 | "Missing Mandatory fields (number and/or callback url)" 79 | ); 80 | } 81 | }); 82 | 83 | it("should allow getting basic numberInsights", () => { 84 | numberInsight.get({ level: "basic", number: "123456789" }, emptyCallback); 85 | 86 | expect(httpClientStub.request).to.have.been.calledWith( 87 | sinon.match({ 88 | host: "api.nexmo.com", 89 | path: 90 | "/ni/basic/json?number=123456789&api_key=some-key&api_secret=some-secret" 91 | }) 92 | ); 93 | }); 94 | 95 | it("should allow getting standard numberInsights", () => { 96 | numberInsight.get( 97 | { level: "standard", number: "123456789" }, 98 | emptyCallback 99 | ); 100 | 101 | expect(httpClientStub.request).to.have.been.calledWith( 102 | sinon.match({ 103 | host: "api.nexmo.com", 104 | path: 105 | "/ni/standard/json?number=123456789&api_key=some-key&api_secret=some-secret" 106 | }) 107 | ); 108 | }); 109 | 110 | it("should allow getting advanced numberInsights", () => { 111 | numberInsight.get( 112 | { 113 | level: "advanced", 114 | number: "123456789", 115 | callback: "http://example.com" 116 | }, 117 | emptyCallback 118 | ); 119 | 120 | expect(httpClientStub.request).to.have.been.calledWith( 121 | sinon.match({ 122 | host: "api.nexmo.com", 123 | path: 124 | "/ni/advanced/async/json?number=123456789&callback=http%3A%2F%2Fexample.com&api_key=some-key&api_secret=some-secret" 125 | }) 126 | ); 127 | }); 128 | 129 | it("should allow getting advancedSync numberInsights", () => { 130 | numberInsight.get( 131 | { level: "advancedSync", number: "123456789" }, 132 | emptyCallback 133 | ); 134 | 135 | expect(httpClientStub.request).to.have.been.calledWith( 136 | sinon.match({ 137 | host: "api.nexmo.com", 138 | path: 139 | "/ni/advanced/json?number=123456789&api_key=some-key&api_secret=some-secret" 140 | }) 141 | ); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /test/Pricing-test.js: -------------------------------------------------------------------------------- 1 | import Pricing from "../lib/Pricing"; 2 | import { expect, sinon, TestUtils } from "./NexmoTestUtils"; 3 | 4 | // 5 | describe("Pricing", function() { 6 | beforeEach(function() { 7 | this.sandbox = sinon.sandbox.create(); 8 | this.httpClientStub = TestUtils.getHttpClient(); 9 | this.sandbox.stub(this.httpClientStub, "request"); 10 | this.pricing = new Pricing(TestUtils.getCredentials(), { 11 | rest: this.httpClientStub 12 | }); 13 | }); 14 | 15 | afterEach(function() { 16 | this.sandbox.restore(); 17 | }); 18 | 19 | describe("get", function() { 20 | it("should call the correct endpoint", function() { 21 | return expect(this.pricing) 22 | .method("get") 23 | .withParams("sms", "GB") 24 | .to.get.url("/account/get-pricing/outbound/sms?country=GB"); 25 | }); 26 | }); 27 | 28 | describe("getPrefix", function() { 29 | it("should call the correct endpoint", function() { 30 | return expect(this.pricing) 31 | .method("getPrefix") 32 | .withParams("sms", "44") 33 | .to.get.url("/account/get-prefix-pricing/outbound/sms?prefix=44"); 34 | }); 35 | }); 36 | 37 | describe("getFull", function() { 38 | it("should call the correct endpoint", function() { 39 | return expect(this.pricing) 40 | .method("getFull") 41 | .withParams("sms") 42 | .to.get.url("/account/get-full-pricing/outbound/sms"); 43 | }); 44 | }); 45 | 46 | describe("getPhone", function() { 47 | it("should call the correct endpoint", function() { 48 | return expect(this.pricing) 49 | .method("getPhone") 50 | .withParams("sms", "442038659460") 51 | .to.get.url( 52 | "/account/get-phone-pricing/outbound/sms?phone=442038659460" 53 | ); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/Redact-test.js: -------------------------------------------------------------------------------- 1 | import Redact from "../lib/Redact"; 2 | import { expect, sinon, TestUtils } from "./NexmoTestUtils"; 3 | 4 | describe("Redact", function() { 5 | beforeEach(function() { 6 | this.httpClientStub = TestUtils.getHttpClient(); 7 | sinon.stub(this.httpClientStub, "request"); 8 | this.redact = new Redact(TestUtils.getCredentials(), { 9 | api: this.httpClientStub 10 | }); 11 | }); 12 | 13 | afterEach(function() { 14 | this.httpClientStub.request.restore(); 15 | }); 16 | 17 | describe("#transaction", function() { 18 | it("should work with no optional fields", function() { 19 | return expect(this.redact) 20 | .method("transaction") 21 | .withParams("ABC123", "voice") 22 | .to.post.withJsonBody({ id: "ABC123", product: "voice" }) 23 | .to.url(`${Redact.PATH}/transaction`); 24 | }); 25 | 26 | it("should pass through optional fields", function() { 27 | return expect(this.redact) 28 | .method("transaction") 29 | .withParams("ABC123", "voice", { type: "outbound" }) 30 | .to.post.withJsonBody({ 31 | id: "ABC123", 32 | product: "voice", 33 | type: "outbound" 34 | }) 35 | .to.url(`${Redact.PATH}/transaction`); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/ResourceTestHelper.js: -------------------------------------------------------------------------------- 1 | import querystring from "querystring"; 2 | 3 | class ResourceTestHelper { 4 | static getRequestArgs(params, overrides = {}) { 5 | var callsArgs = { 6 | host: overrides.host || "api.nexmo.com", 7 | path: overrides.path || "/v1/calls", 8 | method: overrides.method || "POST", 9 | body: overrides.hasOwnProperty("body") 10 | ? overrides.body 11 | : JSON.stringify(params), 12 | headers: overrides.headers || { 13 | "Content-Type": "application/json", 14 | Authorization: "Bearer " 15 | } 16 | }; 17 | 18 | // Removed undefined properties 19 | Object.keys(callsArgs).forEach(function(key) { 20 | if (callsArgs[key] === undefined) { 21 | delete callsArgs[key]; 22 | } 23 | }); 24 | 25 | return callsArgs; 26 | } 27 | 28 | static requestArgsMatch(params, requestOverrides) { 29 | return function(actual) { 30 | var expected; 31 | if (requestOverrides) { 32 | expected = ResourceTestHelper.getRequestArgs(params, requestOverrides); 33 | } else { 34 | expected = params; 35 | } 36 | 37 | // We strip api_key and api_secret out of `path` so that our tests 38 | // only look for specific parameters 39 | var qs = actual.path.split("?"); 40 | var qsParts = querystring.parse(qs[1]); 41 | delete qsParts["api_key"]; 42 | delete qsParts["api_secret"]; 43 | if (Object.keys(qsParts).length) { 44 | actual.path = qs[0] + "?" + querystring.stringify(qsParts); 45 | } 46 | 47 | var match = true; 48 | 49 | // Check response parameters 50 | ["host", "path", "method", "body"].forEach(function(k) { 51 | if (expected[k]) { 52 | match = match && expected[k] == actual[k]; 53 | } 54 | }); 55 | 56 | // Also check for any headers that we're expecting 57 | expected.headers = expected.headers || {}; 58 | Object.keys(expected.headers).forEach(function(k) { 59 | // We have a special check for authorization 60 | if (k === "Authorization") { 61 | return true; 62 | } 63 | 64 | match = match && expected.headers[k] === actual.headers[k]; 65 | }); 66 | 67 | // For Authorization we only check the beginning as JWTs are 68 | // dynamically created 69 | if (expected.headers["Authorization"]) { 70 | match = 71 | match && 72 | actual.headers["Authorization"].indexOf( 73 | expected.headers["Authorization"] 74 | ) === 0; 75 | } 76 | 77 | return match; 78 | }; 79 | } 80 | } 81 | 82 | export default ResourceTestHelper; 83 | -------------------------------------------------------------------------------- /test/ShortCode-test.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from "chai"; 2 | import path from "path"; 3 | import sinon from "sinon"; 4 | import sinonChai from "sinon-chai"; 5 | 6 | import ShortCode from "../lib/ShortCode"; 7 | import HttpClient from "../lib/HttpClient"; 8 | import Credentials from "../lib/Credentials"; 9 | 10 | chai.use(sinonChai); 11 | 12 | var creds = Credentials.parse({ 13 | apiKey: "some-key", 14 | apiSecret: "some-secret" 15 | }); 16 | var emptyCallback = () => {}; 17 | 18 | describe("ShortCode", () => { 19 | var httpClientStub = null; 20 | var shortcode = null; 21 | 22 | beforeEach(() => { 23 | httpClientStub = sinon.createStubInstance(HttpClient); 24 | var options = { 25 | httpClient: httpClientStub, 26 | logger: { 27 | info: () => {} 28 | } 29 | }; 30 | shortcode = new ShortCode(creds, options); 31 | }); 32 | 33 | it("should throw if there is no recipient number", () => { 34 | let callback = sinon.spy(); 35 | shortcode.shortcodeAlert(undefined, {}, undefined, callback); 36 | 37 | expect(callback).to.have.been.calledWith( 38 | sinon.match(new Error(ShortCode.ERROR_MESSAGES.to)), 39 | undefined 40 | ); 41 | }); 42 | 43 | it("should throw if there are no message parameters", () => { 44 | try { 45 | shortcode.shortcodeAlert("undefined", undefined, undefined); 46 | } catch (e) { 47 | expect(e.toString()).to.include("Invalid shortcode message parameters"); 48 | } 49 | }); 50 | 51 | it("should allow sending an alert via shortcode", () => { 52 | let recipient = "123456789"; 53 | let messageParams = { some: "param" }; 54 | let opts = { type: "text" }; 55 | shortcode.shortcodeAlert(recipient, messageParams, opts, emptyCallback); 56 | 57 | expect(httpClientStub.request).to.have.been.calledWith( 58 | sinon.match({ 59 | host: "rest.nexmo.com", 60 | path: 61 | "/sc/us/alert/json?type=text&some=param&to=123456789&api_key=some-key&api_secret=some-secret" 62 | }), 63 | "POST" 64 | ); 65 | }); 66 | 67 | it("should allow host override when sending an alert via shortcode", () => { 68 | let recipient = "123456789"; 69 | let messageParams = { some: "param" }; 70 | let opts = { type: "text" }; 71 | httpClientStub = sinon.createStubInstance(HttpClient); 72 | var options = { 73 | httpClient: httpClientStub, 74 | restHost: "rest.example.com", 75 | logger: { 76 | info: () => {} 77 | } 78 | }; 79 | let shortcode = new ShortCode(creds, options); 80 | shortcode.shortcodeAlert(recipient, messageParams, opts, emptyCallback); 81 | 82 | expect(httpClientStub.request).to.have.been.calledWith( 83 | sinon.match({ 84 | host: "rest.example.com", 85 | path: 86 | "/sc/us/alert/json?type=text&some=param&to=123456789&api_key=some-key&api_secret=some-secret" 87 | }), 88 | "POST" 89 | ); 90 | }); 91 | 92 | it("should allow sending a 2fa message via shortcode", () => { 93 | let recipient = "123456789"; 94 | let messageParams = { some: "param" }; 95 | let opts = { type: "text" }; 96 | shortcode.shortcode2FA(recipient, messageParams, opts, emptyCallback); 97 | 98 | expect(httpClientStub.request).to.have.been.calledWith( 99 | sinon.match({ 100 | host: "rest.nexmo.com", 101 | path: 102 | "/sc/us/2fa/json?type=text&some=param&to=123456789&api_key=some-key&api_secret=some-secret" 103 | }), 104 | "POST" 105 | ); 106 | }); 107 | 108 | it("should allow host override when sending a 2fa message via shortcode", () => { 109 | let recipient = "123456789"; 110 | let messageParams = { some: "param" }; 111 | let opts = { type: "text" }; 112 | httpClientStub = sinon.createStubInstance(HttpClient); 113 | var options = { 114 | httpClient: httpClientStub, 115 | restHost: "rest.example.com", 116 | logger: { 117 | info: () => {} 118 | } 119 | }; 120 | let shortcode = new ShortCode(creds, options); 121 | shortcode.shortcode2FA(recipient, messageParams, opts, emptyCallback); 122 | 123 | expect(httpClientStub.request).to.have.been.calledWith( 124 | sinon.match({ 125 | host: "rest.example.com", 126 | path: 127 | "/sc/us/2fa/json?type=text&some=param&to=123456789&api_key=some-key&api_secret=some-secret" 128 | }), 129 | "POST" 130 | ); 131 | }); 132 | 133 | it("should allow sending a marketing message via shortcode", () => { 134 | let recipient = "123456789"; 135 | let messageParams = { some: "param" }; 136 | let opts = { type: "text" }; 137 | shortcode.shortcodeMarketing(recipient, messageParams, opts, emptyCallback); 138 | 139 | expect(httpClientStub.request).to.have.been.calledWith( 140 | sinon.match({ 141 | host: "rest.nexmo.com", 142 | path: 143 | "/sc/us/marketing/json?type=text&some=param&to=123456789&api_key=some-key&api_secret=some-secret" 144 | }), 145 | "POST" 146 | ); 147 | }); 148 | 149 | it("should allow host override when sending a marketing message via shortcode", () => { 150 | let recipient = "123456789"; 151 | let messageParams = { some: "param" }; 152 | let opts = { type: "text" }; 153 | httpClientStub = sinon.createStubInstance(HttpClient); 154 | var options = { 155 | httpClient: httpClientStub, 156 | restHost: "rest.example.com", 157 | logger: { 158 | info: () => {} 159 | } 160 | }; 161 | let shortcode = new ShortCode(creds, options); 162 | shortcode.shortcodeMarketing(recipient, messageParams, opts, emptyCallback); 163 | 164 | expect(httpClientStub.request).to.have.been.calledWith( 165 | sinon.match({ 166 | host: "rest.example.com", 167 | path: 168 | "/sc/us/marketing/json?type=text&some=param&to=123456789&api_key=some-key&api_secret=some-secret" 169 | }), 170 | "POST" 171 | ); 172 | }); 173 | }); 174 | -------------------------------------------------------------------------------- /test/StreamResource-test.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from "chai"; 2 | import path from "path"; 3 | import sinon from "sinon"; 4 | import sinonChai from "sinon-chai"; 5 | 6 | import ResourceTestHelper from "./ResourceTestHelper"; 7 | 8 | import StreamResource from "../lib/StreamResource"; 9 | import HttpClient from "../lib/HttpClient"; 10 | import Credentials from "../lib/Credentials"; 11 | 12 | chai.use(sinonChai); 13 | 14 | var creds = Credentials.parse({ 15 | applicationId: "some-id", 16 | privateKey: path.join(__dirname, "private-test.key") 17 | }); 18 | var emptyCallback = () => {}; 19 | 20 | describe("StreamResource", () => { 21 | var httpClientStub = null; 22 | var stream = null; 23 | 24 | beforeEach(() => { 25 | httpClientStub = sinon.createStubInstance(HttpClient); 26 | var options = { 27 | httpClient: httpClientStub 28 | }; 29 | stream = new StreamResource(creds, options); 30 | }); 31 | 32 | it("should allow a stream to be started", () => { 33 | const callId = "2342342-lkjhlkjh-32423"; 34 | var params = { 35 | stream_url: "https://example.com/▶tést.mp3" // eslint-disable-line camelcase 36 | }; // eslint-disable-line camelcase 37 | stream.start(callId, params, emptyCallback); 38 | 39 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch(params, { 40 | path: StreamResource.PATH.replace("{call_uuid}", callId), 41 | method: "PUT", 42 | headers: { 43 | "Content-Type": "application/json", 44 | "Content-Length": 49 45 | } 46 | }); 47 | 48 | expect(httpClientStub.request).to.have.been.calledWith( 49 | sinon.match(expectedRequestArgs), 50 | emptyCallback 51 | ); 52 | }); 53 | 54 | it("should support host override in stream.start", () => { 55 | let httpClientStub = sinon.createStubInstance(HttpClient); 56 | let options = { 57 | httpClient: httpClientStub, 58 | apiHost: "api.example.com" 59 | }; 60 | let stream = new StreamResource(creds, options); 61 | const callId = "2342342-lkjhlkjh-32423"; 62 | stream.start(callId, {}, emptyCallback); 63 | 64 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch( 65 | {}, 66 | { 67 | path: StreamResource.PATH.replace("{call_uuid}", callId), 68 | method: "PUT", 69 | host: "api.example.com", 70 | headers: { 71 | "Content-Type": "application/json" 72 | } 73 | } 74 | ); 75 | 76 | expect(httpClientStub.request).to.have.been.calledWith( 77 | sinon.match(expectedRequestArgs), 78 | emptyCallback 79 | ); 80 | }); 81 | 82 | it("should be possible to stop a stream", () => { 83 | const callId = "2342342-lkjhlkjh-32423"; 84 | stream.stop(callId, emptyCallback); 85 | 86 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch(null, { 87 | method: "DELETE", 88 | body: undefined, 89 | path: StreamResource.PATH.replace("{call_uuid}", callId) 90 | }); 91 | 92 | expect(httpClientStub.request).to.have.been.calledWith( 93 | sinon.match(expectedRequestArgs), 94 | emptyCallback 95 | ); 96 | }); 97 | 98 | it("should support host override on stream.stop", () => { 99 | let httpClientStub = sinon.createStubInstance(HttpClient); 100 | let options = { 101 | httpClient: httpClientStub, 102 | apiHost: "api.example.com" 103 | }; 104 | let stream = new StreamResource(creds, options); 105 | const callId = "2342342-lkjhlkjh-32423"; 106 | stream.stop(callId, emptyCallback); 107 | 108 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch(null, { 109 | method: "DELETE", 110 | body: undefined, 111 | host: "api.example.com", 112 | path: StreamResource.PATH.replace("{call_uuid}", callId) 113 | }); 114 | 115 | expect(httpClientStub.request).to.have.been.calledWith( 116 | sinon.match(expectedRequestArgs), 117 | emptyCallback 118 | ); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /test/TalkResource-test.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from "chai"; 2 | 3 | import path from "path"; 4 | import sinon from "sinon"; 5 | import sinonChai from "sinon-chai"; 6 | 7 | import ResourceTestHelper from "./ResourceTestHelper"; 8 | 9 | import TalkResource from "../lib/TalkResource"; 10 | import HttpClient from "../lib/HttpClient"; 11 | import Credentials from "../lib/Credentials"; 12 | 13 | chai.use(sinonChai); 14 | var creds = Credentials.parse({ 15 | applicationId: "some-id", 16 | privateKey: path.join(__dirname, "private-test.key") 17 | }); 18 | var emptyCallback = () => {}; 19 | 20 | describe("TalkResource", () => { 21 | var httpClientStub = null; 22 | var talk = null; 23 | 24 | beforeEach(() => { 25 | httpClientStub = sinon.createStubInstance(HttpClient); 26 | var options = { 27 | httpClient: httpClientStub 28 | }; 29 | talk = new TalkResource(creds, options); 30 | }); 31 | 32 | it("should be able to start a talk", () => { 33 | const callId = "2342342-lkjhlkjh-32423"; 34 | var params = { 35 | text: "Hello!" 36 | }; 37 | talk.start(callId, params, emptyCallback); 38 | 39 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch(params, { 40 | path: TalkResource.PATH.replace("{call_uuid}", callId), 41 | method: "PUT", 42 | headers: { 43 | "Content-Type": "application/json", 44 | "Content-Length": 17 45 | } 46 | }); 47 | expect(httpClientStub.request).to.have.been.calledWith( 48 | sinon.match(expectedRequestArgs), 49 | emptyCallback 50 | ); 51 | }); 52 | 53 | it("should support host override on talk.start", () => { 54 | let httpClientStub = sinon.createStubInstance(HttpClient); 55 | let options = { 56 | httpClient: httpClientStub, 57 | apiHost: "api.example.com" 58 | }; 59 | let talk = new TalkResource(creds, options); 60 | const callId = "2342342-lkjhlkjh-32423"; 61 | talk.start(callId, {}, emptyCallback); 62 | 63 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch( 64 | {}, 65 | { 66 | path: TalkResource.PATH.replace("{call_uuid}", callId), 67 | method: "PUT", 68 | host: "api.example.com", 69 | headers: { 70 | "Content-Type": "application/json" 71 | } 72 | } 73 | ); 74 | expect(httpClientStub.request).to.have.been.calledWith( 75 | sinon.match(expectedRequestArgs), 76 | emptyCallback 77 | ); 78 | }); 79 | 80 | it("should be able to start a talk with unicode characters", () => { 81 | const callId = "2342342-lkjhlkjh-32423"; 82 | var params = { 83 | text: "Alô 😊!" 84 | }; 85 | talk.start(callId, params, emptyCallback); 86 | 87 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch(params, { 88 | path: TalkResource.PATH.replace("{call_uuid}", callId), 89 | method: "PUT", 90 | headers: { 91 | "Content-Type": "application/json", 92 | "Content-Length": 21 93 | } 94 | }); 95 | expect(httpClientStub.request).to.have.been.calledWith( 96 | sinon.match(expectedRequestArgs), 97 | emptyCallback 98 | ); 99 | }); 100 | 101 | it("should be possible to stop an ongoing talk", () => { 102 | const callId = "2342342-lkjhlkjh-32423"; 103 | talk.stop(callId, emptyCallback); 104 | 105 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch(null, { 106 | method: "DELETE", 107 | body: undefined, 108 | path: TalkResource.PATH.replace("{call_uuid}", callId) 109 | }); 110 | 111 | expect(httpClientStub.request).to.have.been.calledWith( 112 | sinon.match(expectedRequestArgs), 113 | emptyCallback 114 | ); 115 | }); 116 | 117 | it("should support host override on talk.stop", () => { 118 | let httpClientStub = sinon.createStubInstance(HttpClient); 119 | let options = { 120 | httpClient: httpClientStub, 121 | apiHost: "api.example.com" 122 | }; 123 | let talk = new TalkResource(creds, options); 124 | const callId = "2342342-lkjhlkjh-32423"; 125 | talk.stop(callId, emptyCallback); 126 | 127 | var expectedRequestArgs = ResourceTestHelper.requestArgsMatch(null, { 128 | method: "DELETE", 129 | host: "api.example.com", 130 | body: undefined, 131 | path: TalkResource.PATH.replace("{call_uuid}", callId) 132 | }); 133 | 134 | expect(httpClientStub.request).to.have.been.calledWith( 135 | sinon.match(expectedRequestArgs), 136 | emptyCallback 137 | ); 138 | }); 139 | }); 140 | -------------------------------------------------------------------------------- /test/Utils-test.js: -------------------------------------------------------------------------------- 1 | import Utils from "../src/Utils"; 2 | import expect from "expect.js"; 3 | import sinon from "sinon"; 4 | 5 | describe("Utils", () => { 6 | describe(".createPathWithQuery", () => { 7 | it("should throw error if query isn't present", () => { 8 | expect(Utils.createPathWithQuery).to.throwError(); 9 | }); 10 | 11 | it("should return path if query is string", () => { 12 | expect(Utils.createPathWithQuery("some", "thing")).to.equal("some/thing"); 13 | }); 14 | 15 | it("should return path if query is empty object", () => { 16 | expect(Utils.createPathWithQuery("some", {})).to.equal("some"); 17 | }); 18 | 19 | it("should return path with query if query is an object", () => { 20 | expect( 21 | Utils.createPathWithQuery("some", { things: "neverChange" }) 22 | ).to.equal("some?things=neverChange"); 23 | }); 24 | }); 25 | 26 | describe(".sendError", () => { 27 | it("should throw error", () => { 28 | expect(Utils.sendError).to.throwError(); 29 | }); 30 | 31 | it("should throw call the callback", () => { 32 | let callback = sinon.spy(); 33 | Utils.sendError(callback); 34 | expect(callback.calledWith(undefined, undefined)); 35 | }); 36 | }); 37 | 38 | describe(".clone", () => { 39 | it("should clone object", () => { 40 | expect(JSON.stringify(Utils.clone({}))).to.equal("{}"); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/Verify-test.js: -------------------------------------------------------------------------------- 1 | import Verify from "../lib/Verify"; 2 | import chai, { expect } from "chai"; 3 | import path from "path"; 4 | import sinon from "sinon"; 5 | import sinonChai from "sinon-chai"; 6 | 7 | import HttpClient from "../lib/HttpClient"; 8 | import Credentials from "../lib/Credentials"; 9 | 10 | import ResourceTestHelper from "./ResourceTestHelper"; 11 | 12 | chai.use(sinonChai); 13 | 14 | var creds = Credentials.parse({ 15 | apiKey: "some-key", 16 | apiSecret: "some-secret" 17 | }); 18 | var emptyCallback = () => {}; 19 | 20 | describe("Verify", () => { 21 | var httpClientStub = null; 22 | var verify = null; 23 | 24 | beforeEach(() => { 25 | httpClientStub = sinon.createStubInstance(HttpClient); 26 | var options = { 27 | httpClient: httpClientStub, 28 | logger: { 29 | info: () => {} 30 | } 31 | }; 32 | verify = new Verify(creds, options); 33 | }); 34 | 35 | it("should throw if there is no params for request", () => { 36 | try { 37 | verify.request({}); 38 | } catch (e) { 39 | expect(e.toString()).to.include( 40 | "Missing Mandatory fields (number and/or brand)" 41 | ); 42 | } 43 | }); 44 | 45 | it("should throw if there is no params for check", () => { 46 | try { 47 | verify.check({}); 48 | } catch (e) { 49 | expect(e.toString()).to.include( 50 | "Missing Mandatory fields (request_id and/or code)" 51 | ); 52 | } 53 | }); 54 | 55 | it("should throw if there is no params for control", () => { 56 | try { 57 | verify.control({}); 58 | } catch (e) { 59 | expect(e.toString()).to.include( 60 | "Missing Mandatory fields (request_id and/or cmd-command)" 61 | ); 62 | } 63 | }); 64 | 65 | it("should throw if there is no params for search", () => { 66 | try { 67 | verify.search(); 68 | } catch (e) { 69 | expect(e.toString()).to.include( 70 | "Missing Mandatory fields (request_id or request_ids)" 71 | ); 72 | } 73 | }); 74 | 75 | it("should allow sending a verify request", () => { 76 | verify.request({ number: "123", brand: "acme" }, emptyCallback); 77 | 78 | expect(httpClientStub.request).to.have.been.calledWith( 79 | sinon.match({ 80 | host: "api.nexmo.com", 81 | path: 82 | "/verify/json?number=123&brand=acme&api_key=some-key&api_secret=some-secret" 83 | }) 84 | ); 85 | }); 86 | 87 | it("should allow sending a psd2verify request", () => { 88 | verify.psd2( 89 | { number: "123", payee: "acme", amount: "amount" }, 90 | emptyCallback 91 | ); 92 | 93 | expect(httpClientStub.request).to.have.been.calledWith( 94 | sinon.match({ 95 | host: "api.nexmo.com", 96 | path: 97 | "/verify/psd2/json?number=123&payee=acme&amount=amount&api_key=some-key&api_secret=some-secret" 98 | }) 99 | ); 100 | }); 101 | 102 | it("should allow checking a verify request", () => { 103 | verify.check({ request_id: "123", code: "1234" }, emptyCallback); 104 | 105 | expect(httpClientStub.request).to.have.been.calledWith( 106 | sinon.match({ 107 | host: "api.nexmo.com", 108 | path: 109 | "/verify/check/json?request_id=123&code=1234&api_key=some-key&api_secret=some-secret" 110 | }) 111 | ); 112 | }); 113 | 114 | it("should allow controling a verify request", () => { 115 | verify.control({ request_id: "123", cmd: "test" }, emptyCallback); 116 | 117 | expect(httpClientStub.request).to.have.been.calledWith( 118 | sinon.match({ 119 | host: "api.nexmo.com", 120 | path: 121 | "/verify/control/json?request_id=123&cmd=test&api_key=some-key&api_secret=some-secret" 122 | }) 123 | ); 124 | }); 125 | 126 | it("should allow searching by id for a verify request", () => { 127 | verify.search("123", emptyCallback); 128 | 129 | expect(httpClientStub.request).to.have.been.calledWith( 130 | sinon.match({ 131 | host: "api.nexmo.com", 132 | path: 133 | "/verify/search/json?request_id=123&api_key=some-key&api_secret=some-secret" 134 | }) 135 | ); 136 | }); 137 | 138 | it("should allow searching by single id array for a verify request", () => { 139 | verify.search(["123"], emptyCallback); 140 | 141 | expect(httpClientStub.request).to.have.been.calledWith( 142 | sinon.match({ 143 | host: "api.nexmo.com", 144 | path: 145 | "/verify/search/json?request_id=123&api_key=some-key&api_secret=some-secret" 146 | }) 147 | ); 148 | }); 149 | 150 | it("should allow searching by ids for a verify request", () => { 151 | verify.search(["123", "456"], emptyCallback); 152 | 153 | expect(httpClientStub.request).to.have.been.calledWith( 154 | sinon.match({ 155 | host: "api.nexmo.com", 156 | path: 157 | "/verify/search/json?request_ids=123&request_ids=456&api_key=some-key&api_secret=some-secret" 158 | }) 159 | ); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /test/Voice-test.js: -------------------------------------------------------------------------------- 1 | import Voice from "../lib/Voice"; 2 | import chai, { expect } from "chai"; 3 | import path from "path"; 4 | import sinon from "sinon"; 5 | import sinonChai from "sinon-chai"; 6 | 7 | import HttpClient from "../lib/HttpClient"; 8 | import Credentials from "../lib/Credentials"; 9 | 10 | import ResourceTestHelper from "./ResourceTestHelper"; 11 | 12 | // var expectedRequestArgs = ResourceTestHelper.requestArgsMatch({ 13 | // path: "/search/messages?ids=1&ids=2" 14 | // }); 15 | 16 | chai.use(sinonChai); 17 | 18 | var creds = Credentials.parse({ 19 | apiKey: "some-key", 20 | apiSecret: "some-secret" 21 | }); 22 | var emptyCallback = () => {}; 23 | 24 | describe("Voice", () => { 25 | var httpClientStub = null; 26 | var voice = null; 27 | 28 | beforeEach(() => { 29 | httpClientStub = sinon.createStubInstance(HttpClient); 30 | var options = { 31 | httpClient: httpClientStub, 32 | logger: { 33 | info: () => {} 34 | } 35 | }; 36 | voice = new Voice(creds, options); 37 | }); 38 | 39 | it("should throw if there is no message for tts request", () => { 40 | try { 41 | voice.sendTTSMessage(); 42 | } catch (e) { 43 | expect(e.toString()).to.include("Invalid Text Message"); 44 | } 45 | }); 46 | 47 | it("should throw if there is no phone number for tts request", () => { 48 | try { 49 | voice.sendTTSMessage(undefined, "message"); 50 | } catch (e) { 51 | expect(e.toString()).to.include("Invalid to address"); 52 | } 53 | }); 54 | 55 | it("should throw if there is no message for tts with capture request", () => { 56 | try { 57 | voice.sendTTSPromptWithCapture(); 58 | } catch (e) { 59 | expect(e.toString()).to.include("Invalid Text Message"); 60 | } 61 | }); 62 | 63 | it("should throw if there is no maxDigits for tts with capture request", () => { 64 | try { 65 | voice.sendTTSPromptWithCapture("123", "message"); 66 | } catch (e) { 67 | expect(e.toString()).to.include("Invalid max digits for TTS prompt"); 68 | } 69 | }); 70 | 71 | it("should throw if there is no byeText for tts with capture request", () => { 72 | try { 73 | voice.sendTTSPromptWithCapture("123", "message", 4); 74 | } catch (e) { 75 | expect(e.toString()).to.include("Invalid bye text for TTS prompt"); 76 | } 77 | }); 78 | 79 | it("should throw if there is no message for tts with confirm request", () => { 80 | try { 81 | voice.sendTTSPromptWithConfirm(); 82 | } catch (e) { 83 | expect(e.toString()).to.include("Invalid Text Message"); 84 | } 85 | }); 86 | 87 | it("should throw if there is no maxDigits for tts with confirm request", () => { 88 | try { 89 | voice.sendTTSPromptWithConfirm("123", "message"); 90 | } catch (e) { 91 | expect(e.toString()).to.include("Invalid max digits for TTS prompt"); 92 | } 93 | }); 94 | 95 | it("should throw if there is no pinCode for tts with confirm request", () => { 96 | try { 97 | voice.sendTTSPromptWithConfirm("123", "message", 4); 98 | } catch (e) { 99 | expect(e.toString()).to.include("Invalid pin code for TTS confirm"); 100 | } 101 | }); 102 | 103 | it("should throw if there is no byeText for tts with confirm request", () => { 104 | try { 105 | voice.sendTTSPromptWithConfirm("123", "message", 4, "1234"); 106 | } catch (e) { 107 | expect(e.toString()).to.include("Invalid bye text for TTS prompt"); 108 | } 109 | }); 110 | 111 | it("should throw if there is no failedText for tts with confirm request", () => { 112 | try { 113 | voice.sendTTSPromptWithConfirm("123", "message", 4, "1234", "bbye"); 114 | } catch (e) { 115 | expect(e.toString()).to.include("Invalid failed text for TTS confirm"); 116 | } 117 | }); 118 | 119 | it("should throw if there is no answerUrl for call request", () => { 120 | try { 121 | voice.call(); 122 | } catch (e) { 123 | expect(e.toString()).to.include("Invalid answer URL for call"); 124 | } 125 | }); 126 | 127 | it("should allow searching by ids for a voice request", () => { 128 | voice.sendTTSMessage("123", "message"); 129 | 130 | expect(httpClientStub.request).to.have.been.calledWith( 131 | sinon.match({ 132 | host: "api.nexmo.com", 133 | path: 134 | "/tts/json?to=123&text=message&api_key=some-key&api_secret=some-secret" 135 | }) 136 | ); 137 | }); 138 | 139 | it("should allow searching by ids for a voice request", () => { 140 | voice.sendTTSPromptWithCapture("123", "message", 1, "bbye"); 141 | 142 | expect(httpClientStub.request).to.have.been.calledWith( 143 | sinon.match({ 144 | host: "api.nexmo.com", 145 | path: 146 | "/tts-prompt/json?to=123&text=message&max_digits=1&bye_text=bbye&api_key=some-key&api_secret=some-secret" 147 | }) 148 | ); 149 | }); 150 | 151 | it("should allow searching by ids for a voice request", () => { 152 | voice.sendTTSPromptWithConfirm("123", "message", 4, "1234", "bbye", "oops"); 153 | 154 | expect(httpClientStub.request).to.have.been.calledWith( 155 | sinon.match({ 156 | host: "api.nexmo.com", 157 | path: 158 | "/tts-prompt/json?to=123&text=message&max_digits=4&pin_code=1234&bye_text=bbye&failed_text=oops&api_key=some-key&api_secret=some-secret" 159 | }) 160 | ); 161 | }); 162 | 163 | it("should allow searching by ids for a voice request", () => { 164 | voice.call("123", "http://example.com"); 165 | 166 | expect(httpClientStub.request).to.have.been.calledWith( 167 | sinon.match({ 168 | host: "rest.nexmo.com", 169 | path: 170 | "/call/json?to=123&answer_url=http%3A%2F%2Fexample.com&api_key=some-key&api_secret=some-secret" 171 | }) 172 | ); 173 | }); 174 | }); 175 | -------------------------------------------------------------------------------- /test/private-test.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDSlV5qFN6UeSP0 3 | rIQjvnNGOayswWRD7VRPqkLDz3fARjOnIeLcQ1TmltvdCv1yJFXcKWGP075+MPjz 4 | 77TAooV+XqMvKAuXQ41J8TkCiBbdxrKhWm5FgUlgwHCIPBST4xfF2id9BGtakKHx 5 | KD6RaJJMrEHgFv4QDcMyZiJtfIGhQFQCHJHTt8zky/kPrQqvDgkwe3L2mTDQIxrc 6 | KVKUdUgzGF5ZFv7K9FCx1ZYZTSbzSgN/9kxXjRETvg1o6JtvWhOf2xlolbNJqzSJ 7 | /gSNN/TUvKcWCHKoGbkdEX6mwtq3YpNgAMmZrDoWprIGSn2+zAxeJipvYIIbXmAh 8 | FbrWIPqpAgMBAAECggEAFpRmkxhZb3Eig63rM0v3X0x4MUPr+i6cpOLRlYkNmEPO 9 | w7kp8p/o9BqxSN0l+j20KSRqGM/FtBE7OH4iPao6MdJ4S+pO0obZ7lebsHj4KV/m 10 | BSVwVSqSPCTA9uf+StyFpDTf7r3xEZJtktv+WCgZe82Co20ZC+P141W5Pk/PNINt 11 | U6JWyJ2Xw+4NIwt2Y3wypZhtZd/OS9N99czWGywiIb+3N/yfdUEyTnjqTz55QcaW 12 | v0LQB71+kyrZKwkWCgLu2019NJMdyuzBIXF9UUK7i+YBRCbkjSHN+bBDGOFfSRN/ 13 | k898zOircuCLc6WKIyi6oLpzJHKscjZXk7RTDGiBgwKBgQDpL2TTKxEzT/VTcoeU 14 | AP17Fi4g33qpfxaJOYIsO45KCOl+bAEKrxh/KkH3MKZgeU4XcWG6sY48BNBRTrvA 15 | H3Ta1AFHL+AQaT94EhmJuV1ypRLfZH7WdutlkS4RGiQ2qBn95JZlWdCiUsTWKoM3 16 | dR5Avhc+Ch7lIjgaFtrLvGFUUwKBgQDnL96R7jAm56UCMPUnWW888CTJUiirCyji 17 | AGUUzMDW6N0PYYpIY1vw0qtKPVQwN79qwpKtFZqjXi9dCUS73pGQqC92O7vPVPog 18 | iNJv+wIJPV8FBIy8VzmqEWuOfS/FqYe3uJ2ezvZQOhwyFNo7bKaJDpQB0GXPbtm9 19 | 4xVp+gpVkwKBgDXa3E5pA+/155L+QJHOKLzkwoKVGp9K2smWnpnYp2Qx6rtn9lWh 20 | WucDu3h7o/FkEsUXSprzpwX1lqcwp+wXV8WycRkf1X/0ztPMWlmUliCEP3eYnjKq 21 | BHPQs/L/Np3o9RfOU23U8HqM4ykTaHrGcdZCENwjOj9xR8O+w4mKWHBBAoGBALov 22 | hyQnm4f5jp6RJx6lnDyLelTYYgA+YJKCluWFcNijeUTl3SQnQOhWAbUsZxzeCL7G 23 | LLJQb9WaCLL4v+49dSX3DSI/QSXc3gRNrIyari0ay+eSnnUgGkSo+uIqV8rsmWyQ 24 | tgAr1ZsO8Z4zxP2OpyBCKVi8qQHB2IJg2+IZiTEZAoGAFtWcEb54TuVlxZyjpGER 25 | T4mPYX3kIxiX7PhL1AqF/zn3Z3tdXNTA5fKXptJwdmlYRQBi2x6lrWVwNc+RXHQi 26 | YtnB4QmqH/zKNL6/EPJtYae4+cKEKyrbPZJeTWtREU9IB3jiUGI5ywtlfFe5iPgE 27 | Ndy4W8YfiDmdUL3V56Ny/8M= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/public-test.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0pVeahTelHkj9KyEI75z 3 | RjmsrMFkQ+1UT6pCw893wEYzpyHi3ENU5pbb3Qr9ciRV3Clhj9O+fjD48++0wKKF 4 | fl6jLygLl0ONSfE5AogW3cayoVpuRYFJYMBwiDwUk+MXxdonfQRrWpCh8Sg+kWiS 5 | TKxB4Bb+EA3DMmYibXyBoUBUAhyR07fM5Mv5D60Krw4JMHty9pkw0CMa3ClSlHVI 6 | MxheWRb+yvRQsdWWGU0m80oDf/ZMV40RE74NaOibb1oTn9sZaJWzSas0if4EjTf0 7 | 1LynFghyqBm5HRF+psLat2KTYADJmaw6FqayBkp9vswMXiYqb2CCG15gIRW61iD6 8 | qQIDAQAB 9 | -----END PUBLIC KEY----- 10 | --------------------------------------------------------------------------------