From e470157fd79281337b9209ea896b024eec268458 Mon Sep 17 00:00:00 2001 From: Shawn Stern Date: Tue, 11 Apr 2023 21:51:36 -0700 Subject: [PATCH] docs: consolidate (#928) * docs: migrate custom http client example from docs * docs: consolidate docs information with README.md * chore: remove old docs reference * chore: remove old docs reference * chore: remove old docs reference --- CHANGES.md | 2 - CONTRIBUTING.md | 9 +- README.md | 181 +++++++++++++++---- advanced-examples/custom-http-client.md | 226 ++++++++++++++++++++++++ 4 files changed, 379 insertions(+), 39 deletions(-) create mode 100644 advanced-examples/custom-http-client.md diff --git a/CHANGES.md b/CHANGES.md index cdd768bb54..e91b3d9ee4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3129,6 +3129,4 @@ The newest version of the `twilio-node` helper library! This version brings a host of changes to update and modernize the `twilio-node` helper library. It is auto-generated to produce a more consistent and correct product. -- [Migration Guide](https://www.twilio.com/docs/libraries/node/migration-guide) - [Full API Documentation](https://twilio.github.io/twilio-node/) -- [General Documentation](https://www.twilio.com/docs/libraries/node) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1a9a98f652..171e93b925 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ it can be. ## Got an API/Product Question or Problem? If you have questions about how to use `twilio-node`, please see our -[docs][docs-link], and if you don't find the answer there, please contact +[docs](./README.md), and if you don't find the answer there, please contact [help@twilio.com](mailto:help@twilio.com) with any issues you have. ## Found an Issue? @@ -68,10 +68,6 @@ you're working on. For large fixes, please build and test the documentation before submitting the PR to be sure you haven't accidentally introduced layout or formatting issues. -If you want to help improve the docs at -[https://www.twilio.com/docs/libraries/node][docs-link], please contact -[help@twilio.com](mailto:help@twilio.com). - ## Submission Guidelines ### Submitting an Issue @@ -156,6 +152,5 @@ you are working: * All classes and methods **must be documented**. -[docs-link]: https://www.twilio.com/docs/libraries/node [issue-link]: https://github.com/twilio/twilio-node/issues/new -[github]: https://github.com/twilio/twilio-node \ No newline at end of file +[github]: https://github.com/twilio/twilio-node diff --git a/README.md b/README.md index 2b168c30c2..44915e87b7 100644 --- a/README.md +++ b/README.md @@ -19,17 +19,45 @@ The Node library documentation can be found [here][libdocs]. This library supports the following Node.js implementations: -* Node.js 14 -* Node.js 16 -* Node.js 18 +- Node.js 14 +- Node.js 16 +- Node.js 18 TypeScript is supported for TypeScript version 2.9 and above. +> **Warning** +> Do not use this Node.js library in a front-end application. Doing so can expose your Twilio credentials to end-users as part of the bundled HTML/JavaScript sent to their browser. + ## Installation `npm install twilio` or `yarn add twilio` -## Sample Usage +### Test your installation + +To make sure the installation was successful, try sending yourself an SMS message, like this: + +```js +// Your AccountSID and Auth Token from console.twilio.com +const accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; +const authToken = 'your_auth_token'; + +const client = require('twilio')(accountSid, authToken); + +client.messages + .create({ + body: 'Hello from twilio-node', + to: '+12345678901', // Text your number + from: '+12345678901', // From a valid Twilio number + }) + .then((message) => console.log(message.sid)); +``` + +After a brief delay, you will receive the text message on your phone. + +> **Warning** +> It's okay to hardcode your credentials when testing locally, but you should use environment variables to keep them secret before committing any code or deploying to production. Check out [How to Set Environment Variables](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html) for more information. + +## Usage Check out these [code examples](examples) in JavaScript and TypeScript to get up and running quickly. @@ -40,27 +68,31 @@ Check out these [code examples](examples) in JavaScript and TypeScript to get up If your environment requires SSL decryption, you can set the path to CA bundle in the env var `TWILIO_CA_BUNDLE`. ### Client Initialization + If you invoke any V2010 operations without specifying an account SID, `twilio-node` will automatically use the `TWILIO_ACCOUNT_SID` value that the client was initialized with. This is useful for when you'd like to, for example, fetch resources for your main account but also your subaccount. See below: ```javascript -var accountSid = process.env.TWILIO_ACCOUNT_SID; // Your Account SID from www.twilio.com/console -var authToken = process.env.TWILIO_AUTH_TOKEN; // Your Auth Token from www.twilio.com/console -var subaccountSid = process.env.TWILIO_ACCOUNT_SUBACCOUNT_SID; // Your Subaccount SID from www.twilio.com/console +// Your Account SID, Subaccount SID Auth Token from console.twilio.com +const accountSid = process.env.TWILIO_ACCOUNT_SID; +const authToken = process.env.TWILIO_AUTH_TOKEN; +const subaccountSid = process.env.TWILIO_ACCOUNT_SUBACCOUNT_SID; const client = require('twilio')(accountSid, authToken); const mainAccountCalls = client.api.v2010.account.calls.list; // SID not specified, so defaults to accountSid -const subaccountCalls = client.api.v2010.account(subaccountSid).calls.list // SID specified as subaccountSid +const subaccountCalls = client.api.v2010.account(subaccountSid).calls.list; // SID specified as subaccountSid ``` ### Lazy Loading `twilio-node` supports lazy loading required modules for faster loading time. Lazy loading is enabled by default. To disable lazy loading, simply instantiate the Twilio client with the `lazyLoading` flag set to `false`: + ```javascript -var accountSid = process.env.TWILIO_ACCOUNT_SID; // Your Account SID from www.twilio.com/console -var authToken = process.env.TWILIO_AUTH_TOKEN; // Your Auth Token from www.twilio.com/console +// Your Account SID and Auth Token from console.twilio.com +const accountSid = process.env.TWILIO_ACCOUNT_SID; +const authToken = process.env.TWILIO_AUTH_TOKEN; const client = require('twilio')(accountSid, authToken, { - lazyLoading: false + lazyLoading: false, }); ``` @@ -71,12 +103,12 @@ const client = require('twilio')(accountSid, authToken, { Optionally, the maximum number of retries performed by this feature can be set with the `maxRetries` flag. The default maximum number of retries is `3`. ```javascript -var accountSid = process.env.TWILIO_ACCOUNT_SID; // Your Account SID from www.twilio.com/console -var authToken = process.env.TWILIO_AUTH_TOKEN; // Your Auth Token from www.twilio.com/console +const accountSid = process.env.TWILIO_ACCOUNT_SID; +const authToken = process.env.TWILIO_AUTH_TOKEN; const client = require('twilio')(accountSid, authToken, { - autoRetry: true, - maxRetries: 3 + autoRetry: true, + maxRetries: 3, }); ``` @@ -85,12 +117,12 @@ const client = require('twilio')(accountSid, authToken, { To take advantage of Twilio's [Global Infrastructure](https://www.twilio.com/docs/global-infrastructure), specify the target Region and/or Edge for the client: ```javascript -var accountSid = process.env.TWILIO_ACCOUNT_SID; // Your Account SID from www.twilio.com/console -var authToken = process.env.TWILIO_AUTH_TOKEN; // Your Auth Token from www.twilio.com/console +const accountSid = process.env.TWILIO_ACCOUNT_SID; +const authToken = process.env.TWILIO_AUTH_TOKEN; const client = require('twilio')(accountSid, authToken, { - region: 'au1', - edge: 'sydney', + region: 'au1', + edge: 'sydney', }); ``` @@ -104,34 +136,123 @@ client.edge = 'sydney'; This will result in the `hostname` transforming from `api.twilio.com` to `api.sydney.au1.twilio.com`. +### Iterate through records + +The library automatically handles paging for you. Collections, such as `calls` and `messages`, have `list` and `each` methods that page under the hood. With both `list` and `each`, you can specify the number of records you want to receive (`limit`) and the maximum size you want each page fetch to be (`pageSize`). The library will then handle the task for you. + +`list` eagerly fetches all records and returns them as a list, whereas `each` streams records and lazily retrieves pages of records as you iterate over the collection. You can also page manually using the `page` method. + +For more information about these methods, view the [auto-generated library docs](https://www.twilio.com/docs/libraries/reference/twilio-node/). + +```js +// Your Account SID and Auth Token from console.twilio.com +const accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; +const authToken = 'your_auth_token'; +const client = require('twilio')(accountSid, authToken); + +client.calls.each((call) => console.log(call.direction)); +``` + ### Enable Debug Logging + There are two ways to enable debug logging in the default HTTP client. You can create an environment variable called `TWILIO_LOG_LEVEL` and set it to `debug` or you can set the logLevel variable on the client as debug: + ```javascript -var accountSid = process.env.TWILIO_ACCOUNT_SID; // Your Account SID from www.twilio.com/console -var authToken = process.env.TWILIO_AUTH_TOKEN; // Your Auth Token from www.twilio.com/console +const accountSid = process.env.TWILIO_ACCOUNT_SID; +const authToken = process.env.TWILIO_AUTH_TOKEN; const client = require('twilio')(accountSid, authToken, { - logLevel: 'debug' + logLevel: 'debug', }); ``` + You can also set the logLevel variable on the client after constructing the Twilio client: + ```javascript const client = require('twilio')(accountSid, authToken); client.logLevel = 'debug'; ``` -## Using webhook validation -See [example](examples/express.js) for a code sample for incoming Twilio request validation. +### Debug API requests -## Handling Exceptions +To assist with debugging, the library allows you to access the underlying request and response objects. This capability is built into the default HTTP client that ships with the library. -For an example on how to handle exceptions in this helper library, please see the [Twilio documentation](https://www.twilio.com/docs/libraries/node#exceptions). +For example, you can retrieve the status code of the last response like so: -## Using a Custom HTTP Client +```js +const accountSid = 'ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; +const authToken = 'your_auth_token'; -To use a custom HTTP client with this helper library, please see the [Twilio documentation](https://www.twilio.com/docs/libraries/node/custom-http-clients-node). +const client = require('twilio')(accountSid, authToken); + +client.messages + .create({ + to: '+14158675309', + from: '+14258675310', + body: 'Ahoy!', + }) + .then(() => { + // Access details about the last request + console.log(client.lastRequest.method); + console.log(client.lastRequest.url); + console.log(client.lastRequest.auth); + console.log(client.lastRequest.params); + console.log(client.lastRequest.headers); + console.log(client.lastRequest.data); + + // Access details about the last response + console.log(client.httpClient.lastResponse.statusCode); + console.log(client.httpClient.lastResponse.body); + }); +``` + +### Handle exceptions + +If the Twilio API returns a 400 or a 500 level HTTP response, `twilio-node` will throw an error including relevant information, which you can then `catch`: + +```js +client.messages + .create({ + body: 'Hello from Node', + to: '+12345678901', + from: '+12345678901', + }) + .then((message) => console.log(message)) + .catch((error) => { + // You can implement your fallback code here + console.log(error); + }); +``` + +or with `async/await`: + +```js +try { + const message = await client.messages.create({ + body: 'Hello from Node', + to: '+12345678901', + from: '+12345678901', + }); + console.log(message); +} catch (error) { + // You can implement your fallback code here + console.error(error); +} +``` + +If you are using callbacks, error information will be included in the `error` parameter of the callback. + +400-level errors are [normal during API operation](https://www.twilio.com/docs/api/rest/request#get-responses) ("Invalid number", "Cannot deliver SMS to that number", for example) and should be handled appropriately. + +### Use a custom HTTP Client + +To use a custom HTTP client with this helper library, please see the [advanced example of how to do so](./advanced-examples/custom-http-client.md). + +### Use webhook validation + +See [example](examples/express.js) for a code sample for incoming Twilio request validation. -## Docker Image +## Docker image The `Dockerfile` present in this repository and its respective `twilio/twilio-node` Docker image are currently used by Twilio for testing purposes only. @@ -149,7 +270,7 @@ Bug fixes, docs, and library improvements are always welcome. Please refer to ou If you're not familiar with the GitHub pull request/contribution process, [this is a nice tutorial](https://gun.io/blog/how-to-github-fork-branch-and-pull-request/). -#### Getting Started +### Get started If you want to familiarize yourself with the project, you can start by [forking the repository](https://help.github.com/articles/fork-a-repo/) and [cloning it in your local development environment](https://help.github.com/articles/cloning-a-repository/). The project requires [Node.js](https://nodejs.org) to be installed on your machine. diff --git a/advanced-examples/custom-http-client.md b/advanced-examples/custom-http-client.md new file mode 100644 index 0000000000..fcc882e942 --- /dev/null +++ b/advanced-examples/custom-http-client.md @@ -0,0 +1,226 @@ +# Custom HTTP Clients for the Twilio Node Helper Library + +If you are working with the Twilio Node.js Helper Library, and you need to modify the HTTP requests that the library makes to the Twilio servers, you’re in the right place. + +The helper library uses [axios](https://www.npmjs.com/package/axios), a promise-based HTTP client, to make requests. You can also provide your own `httpClient` to customize requests as needed. + +The following example shows a typical request without a custom `httpClient`. + +```js +const client = require('twilio')(accountSid, authToken); + +client.messages + .create({ + to: '+15555555555', + from: '+15555555551', + body: 'Ahoy default requestClient!', + }) + .then((message) => console.log(`Message SID ${message.sid}`)) + .catch((error) => console.error(error)); +``` + +Out of the box, the helper library creates a default `RequestClient` for you, using the Twilio credentials you pass to the `init` method. If you have your own `RequestClient`, you can pass it to any Twilio REST API resource action you want. Here’s an example of sending an SMS message with a custom client called `MyRequestClient`. + +```js +// require the Twilio module and MyRequestClient +const twilio = require('twilio'); +const MyRequestClient = require('./MyRequestClient'); + +// Load environment variables +require('dotenv').config(); + +// Twilio Credentials +const accountSid = process.env.ACCOUNT_SID; +const authToken = process.env.AUTH_TOKEN; + +const client = twilio(accountSid, authToken, { + // Custom HTTP Client with a one minute timeout + httpClient: new MyRequestClient(60000), +}); + +client.messages + .create({ + to: '+15555555555', + from: '+15555555551', + body: 'Ahoy, custom requestClient!', + }) + .then((message) => console.log(`Message SID ${message.sid}`)) + .catch((error) => console.error(error)); +``` + +## Create your custom Twilio RestClient + +When you take a closer look at the constructor for `twilio.restClient`, you see that the `httpClient` parameter is a `RequestClient`. This class provides the client to the Twilio helper library to make the necessary HTTP requests. + +Now that you can see how all the components fit together, you can create our own `RequestClient`: + +```js +'use strict'; + +const _ = require('lodash'); +const qs = require('qs'); +const axios = require('axios'); + +/** + * Custom HTTP Client + * Based on: /twilio/lib/base/RequestClient.js + */ +class MyRequestClient { + constructor(timeout) { + this.timeout = timeout; + } + + request(opts) { + opts = opts || {}; + + if (!opts.method) { + throw new Error('http method is required'); + } + + if (!opts.uri) { + throw new Error('uri is required'); + } + + // Axios auth option will use HTTP Basic auth by default + if (opts.username && opts.password) { + this.auth = { + username: opts.username, + password: opts.password, + }; + } + + // Options for axios config + const options = { + url: opts.uri, + method: opts.method, + headers: opts.headers, + auth: this.auth, + timeout: this.timeout, + }; + + // Use 'qs' to support x-www-form-urlencoded with axios + // Construct data request body option for axios config + if (!_.isNull(opts.data)) { + options.headers = { 'content-type': 'application/x-www-form-urlencoded' }; + options.data = qs.stringify(opts.data, { arrayFormat: 'repeat' }); + } + + // Use 'qs' to support x-www-form-urlencoded with axios + // Construct URL params option for axios config + if (!_.isNull(opts.params)) { + options.params = opts.params; + options.paramsSerializer = (params) => { + return qs.stringify(params, { arrayFormat: 'repeat' }); + }; + } + + return axios(options) + .then((response) => { + if (opts.logLevel === 'debug') { + console.log(`response.statusCode: ${response.status}`); + console.log(`response.headers: ${JSON.stringify(response.headers)}`); + } + return { + statusCode: response.status, + body: response.data, + }; + }) + .catch((error) => { + console.error(error); + throw error; + }); + } +} + +module.exports = MyRequestClient; +``` + +## Add a custom timeout + +One common need to alter the HTTP request is to set a custom timeout. In the code sample using a custom client, you will see `httpClient: new MyRequestClient(60000)` where 60000 is the custom timeout value — one minute in milliseconds. + +To make this reusable, here’s a class that you can use to create this `MyRequestClient` whenever you need one. This class is based on the default `RequestClient` provided by the helper library, and uses axios to make requests. + +In this example, we are using some environmental variables loaded at the program's startup to retrieve our credentials: + +- Your Twilio Account Sid and Auth Token ([found here, in the Twilio console](https://console.twilio.com)) + +These settings are located in a `.env` file like so: + +```env +ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +AUTH_TOKEN= your_auth_token +``` + +Here’s the full console program that sends a text message and shows how it all can work together. It loads the `.env` file for us. The timeout value, 60,000 milliseconds, will be used by axios to set the custom timeout. + +```js +// require the Twilio module and MyRequestClient +const twilio = require('twilio'); +const MyRequestClient = require('./MyRequestClient'); + +// Load environment variables +require('dotenv').config(); + +// Twilio Credentials +const accountSid = process.env.ACCOUNT_SID; +const authToken = process.env.AUTH_TOKEN; + +const client = twilio(accountSid, authToken, { + // Custom HTTP Client with a one minute timeout + httpClient: new MyRequestClient(60000), +}); + +client.messages + .create({ + to: '+15555555555', + from: '+15555555551', + body: 'Ahoy, custom requestClient!', + }) + .then((message) => console.log(`Message SID ${message.sid}`)) + .catch((error) => console.error(error)); +``` + +## Call Twilio through a proxy server + +The most common need to alter the HTTP request is to connect and authenticate with an enterprise’s proxy server. The Node.JS Helper library now supports this using the `HTTP_PROXY` environment variable. The Twilio Node.js Helper library uses the [https-proxy-agent](https://www.npmjs.com/package/https-proxy-agent) package to connect with the proxy you assign to the environment variable. + +```env +HTTP_PROXY=http://127.0.0.1:8888 +``` + +If you prefer to use your custom `RequestClient` to connect with a proxy, you could follow the pattern outlined in the code samples on this page. For example, axios supports the use of a proxy with its `proxy` option. The axios proxy takes an object with `protocol`, `host`, and `method` options. These can be passed to your `MyRequestClient` to be used by axios. + +```js +// Pass proxy settings to client constructor +const client = twilio(accountSid, authToken, { + // Custom HTTP Client + httpClient: new MyRequestClient(60000, { + protocol: 'https', + host: '127.0.0.1', + port: 9000, + } + ), +}); + +// Update class to accept a proxy +class MyRequestClient { + constructor(timeout, proxy){ + this.timeout = timeout, + this.proxy = proxy + } + + const options = { + proxy: this.proxy, + // other axios options... + } +} +``` + +## What else can this technique be used for? + +Now that you know how to inject your own `httpClient` into the Twilio API request pipeline, you can use this technique to add custom HTTP headers and authorization to the requests (perhaps as required by an upstream proxy server). + +You could also implement your own `httpClient` to mock the Twilio API responses. With a custom `httpClient`, you can run your unit and integration tests quickly without the need to make a connection to Twilio. In fact, there’s already an example online showing [how to do exactly that with Node.js and Prism](https://www.twilio.com/docs/openapi/mock-api-generation-with-twilio-openapi-spec). + +We can't wait to see what you build!