AirTNG - Rental-by-owner properties fit for a Captain, implemented with Node.js and Express
Clone or download
Pull request Compare This branch is 40 commits ahead, 11 commits behind TwilioDevEd:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
bin
lib
models
public
routes
spec
views
.env.example
.gitignore
.travis.yml
CODE_OF_CONDUCT.md
CONTRIBUTING.md
ISSUE_TEMPLATE.md
LICENSE
PULLREQUEST.md
PULL_REQUEST_TEMPLATE.md
README.md
app.js
config.js
package.json

README.md

Airtng App: Masked Numbers Powered By Bandwidth

This is an exmaple of the reasonable amount of work required to migrate an existing voice and messaging application from Twilio to Bandwidth.

The original Airtng demo can be found over at Twilio's website -or- Github Profile.

Masked Numbers help protect your customers' privacy by creating a seamless interaction by provisioning Bandwidth numbers on the fly. Route all voice calls and messages through your very own 3rd party. This allows you to control the interaction between your customers while putting your customer's privacy first.

Migrating to Bandwidth

Migration to Bandwidth for Masked Calling requires a bit more work and some understanding of the functional differences between the two APIs.

This guide covers:

Callbacks

Similarrly to Twilio, You will need to configure Bandwidth to send requests to your application when SMSs are received.

You will need to provision at least one Bandwidth number so the application's users can make property reservations.

You can buy a number on the My Numbers Tab within your dashboard

Order Phone Number

OrderPhoneNumber

Once you have a number you need to configure it to work with your application.

Local Development (Launch Ngrok)

ngrok http 3000

Keep in mind that our endpoint is:

http://example.ngrok.io/reservations/handle

Create Notification Application

We need to configure where to send SMS webooks for the phone number we just ordered.

Open the My Apps Tab within your dashboard. Create a new application and set the Http Callback Method to GET and set the Messaging URL to the Ngrok url.

Once the Bandwidth Application is created, you need to add the phone number ordered above to the Bandwidth Application

Configure Notification

Create Masked Number Application

We now need to create a different Bandwidth Application (instead of a TwiML App) to manage the newly created Masked Numbers.

This application will provide our masked number app with both SMS webooks and Voice webooks.

Create the application and take note of the Application ID as we will use that when ordering new phone numbers.

# Your SMS Webhook URL
http://example.ngrok.io/commuter/use-sms

# Your Voice Webhook URL
http://example.ngrok.io/commuter/use-voice

Configure Masked Numbers

Update all require('twilio'); with node-bandwidth

Any file with:

Twilio Code
//Twilio Code
var client = require('twilio')(config.accountSid, config.authToken);

Should be updated with the Bandwidth Library and client

Bandwidth Code
//Bandwidth Code
var bandwidth = require('node-bandwidth');
var client = new bandwidth({
  userId: config.userId,
  apiToken: config.apiToken,
  apiSecret: config.apiSecret
});

Update lib/notifier.js to use Bandwidth

notifier.js sends the property owner a text message to confirm or deny a new booking. All messages are sent throught the env_var BANDWIDTH_PHONE_NUMBER;

Twilio Code
//Twilio Code
client.messages.create({
  to: phoneNumber(owner),
  from: config.phoneNumber,
  body: buildMessage(reservation)
})
.then(function(res) {
  console.log(res.body);
})

Becomes

Bandwidth Code
//Bandwidth Code
client.Message.send({
  to: phoneNumber(owner),
  from: config.phoneNumber,
  text: buildMessage(reservation)
})
.then(function (message) {
  console.log(message.id);
})

Bandwidth's API uses text instead of body for message content. Bandwidth's SDK also returns a message response instead of a body.

Update lib/purchaser.js

purchaser.js is responsible for searching and ordering phone numbers. Each time a new user comes on board, they're allocated a new phone number to mask their actual phone number.

Search for Available Numbers

The Twilio code to search for numbers:

Twilio Code
//Twilio Code
client.availablePhoneNumbers('US').local.get({
      areaCode: areaCode,
      voiceEnabled: true,
      smsEnabled: true
})

Becomes

Bandwidth Code
//Bandwidth Code
client.AvailableNumber.search("local", {
  areaCode : areaCode,
  quantity : 1
})

All of Bandwidth's phone numbers are fully voice and sms enabled by default, so we only need to search local by areaCode.

Order Available Number Found

The return types for searching numbers are different. But still very similar.

Twilio Code
//Twilio Code
function(searchResults) {
    if (searchResults.availablePhoneNumbers.length === 0) {
      throw { message: 'No numbers found with that area code' };
    }

    return client.incomingPhoneNumbers.create({
      phoneNumber: searchResults.availablePhoneNumbers[0].phoneNumber,
      voiceApplicationSid: config.applicationSid,
      smsApplicationSid: config.applicationSid
    });
  })
  .then(function(number) {
    return number.phone_number;
  });

Becomes

Bandwidth Code
//Bandwidth Code
function (numbers) {
    if(numbers.length === 0) {
      throw { message: 'No numbers found with that area code' };
    }
    phoneNumber = numbers[0].number;
    return client.PhoneNumber.create({
        number: phoneNumber,
        applicationId: config.applicationId
    });
  })
  .then(function (number) {
    console.log(number.id);
    return phoneNumber;
  });

Note that Bandwidth phone numbers can ONLY be assigned to a single Bandwidth Application for both voice and text.

However, you can specify different callback URLS for messaging and voice within a single Bandwidth Applicaiton.

Update routes/commuter.js

commuter.js contains the logic to actually mask the phone numbers. commuter.js used TwiML to control the call, we'll walk through how to change to BXML.

BXML requires GET webhooks

Since BXML requires your application setup to receive GET requests from Bandwidth, we need to change the routes from router.post to router.get. We also no longer need to validate Twilio webhooks, since we're replacing the Twilio client with Bandwidth.

Twilio Code
// SMS Route
router.post('/use-sms', twilio.webhook({ validate: false }), function (req, res) {...})

// Voice Route
router.post('/use-voice', twilio.webhook({ validate: false }), function (req, res) {...})

Becomes

Bandwidth Code

// SMS Route
router.get('/use-sms', function (req, res) {...})

// Voice Route
router.get('/use-voice', function (req, res) {...})

Update from, to, body values to pull from Query Params

Again, since Bandwidth sends a GET request to our server, we'll need to update how the values are refernced.

Twilio Code
// SMS Values
var from = req.body.From;
var to   = req.body.To;
var body = req.body.Body;

// Voice Values
var from = req.body.From;
var to   = req.body.To;

Becomes

Bandwidth Code
// SMS Values
var from = req.query.from;
var to   = req.query.to;
var body = req.query.text;

// Voice Values
var from = req.query.from;
var to   = req.query.to;
var eventType = req.query.eventType;

NOTE Bandwidth only needs to process BXML on the answer event. We need to ignore other incoming events like incomingCall and Hangup (for this scenario)

Update SMS TwiML to SMS BXML

BXML and TwiML both provide the same features, but are a bit different.

Twilio Code
var twiml = new twilio.TwimlResponse();
twiml.message(body, { to: outgoingPhoneNumber });

res.type('text/xml');
res.send(twiml.toString());

Becomes

Bandwidth Code
var bxml = new bandwidth.BXMLResponse();
bxml.sendMessage(body, {
  to: outgoingPhoneNumber,
  from: to
});
res.type('text/xml');
res.send(bxml.toString());

NOTE Bandwidth BXML requires both a to and from number. It doesn't default any values.

Update Voice TwiML to Voice BXML

TwiML uses the <dial> and <play> verbs to transfer a call and play a media file. Bandwidth uses <transfer> and <playAudio> verbs.

Twilio Code
var twiml = new twilio.TwimlResponse();
twiml.play('http://howtodocs.s3.amazonaws.com/howdy-tng.mp3');
twiml.dial(outgoingPhoneNumber);

res.type('text/xml');
res.send(twiml.toString());

Becomes

Bandwidth Code
if (eventType === 'answer') {
  var bxml = new bandwidth.BXMLResponse();
  bxml.playAudio('http://howtodocs.s3.amazonaws.com/howdy-tng.mp3');
  bxml.transfer({
      transferTo: outgoingPhoneNumber,
      transferCallerId: to
    });
  res.type('text/xml');
  res.send(bxml.toString());
}
else {
  res.sendStatus(200);
}

NOTE Bandwidth BXML Requires transferCallerId to display anything besides the original callerid for the <transfer> verb. Also, we only respond to the answer eventType. The other call events, are just sent 200 OK.

Update routes/reservations.js

reservations.js includes the code to handle responses sent to the property owner to either approve or deny the reservation request.

We'll need to do the same thing we did for commuter.js SMS TwiML Response: Update POST to GET and change the body to text.

Update POST to GET

Twilio Code
router.post('/handle', twilio.webhook({validate: false}), function (req, res) {...})
Bandwidth Code
router.get('/handle', function (req, res) {...})

Update from, to, body values to pull from Query Params

Twilio Code
var from = req.body.From;
var smsRequest = req.body.Body;
Bandwidth Code
var from = req.query.from;
var to   = req.query.to;
var smsRequest = req.query.text;

Update the TwiML to BXML

Since Twilio has different default behaviour for TwiML than Bandwidth does for BXML, we need to keep track of the from, to, and text fields from the incoming message.

Bandwidth Code
// Be sure to swap the direction
var smsResponse = {
  to: from,
  from: to
};

Within the respond function, we need to modify to accept a JSON object instead of just a string message.

Twilio Code
var respond = function(res, message) {
  var twiml = new twilio.TwimlResponse();
  twiml.message(message);

  res.type('text/xml');
  res.send(twiml.toString());
}

Becomes

Bandwidth Code
var respond = function(res, smsResponse) {
  var bxml = new bandwidth.BXMLResponse();
    bxml.sendMessage(smsResponse.message, {
      to: smsResponse.to,
      from: smsResponse.from
    });
    res.type('text/xml');
    res.send(bxml.toString());
}

Read more

For a detailed side-by-side comparison. Take a look at the Pull Request on Github

Database

  1. This sample application stores data in a MongoDB database using Mongoose. You can download and run MongoDB yourself (OS X, Linux, Windows).

    On OS X, maybe the easiest way to get MongoDB running locally is to install via Homebrew.

    $ brew install mongodb

    You should then be able to run a local server with:

    $ mongod

Running

  1. Clone this repository and cd into it.

    $ git clone git@github.com:BandwidthExamples/airtng-node.git
    $ cd airtng-node
  2. Copy the sample configuration file and edit it to match your configuration.

    $ cp .env.example .env

You can find your BANDWIDTH_USER_ID, BANDWIDTH_API_TOKEN and BANDWIDTH_API_SECRET in your Account Tab

Cred

You will also need a BANDWIDTH_PHONE_NUMBER which you ordered in the setup above.

Use the Bandwidth Application-ID applicationId obtained in step 3 in the BANDWIDTH_APPLICATION_ID variable.

  1. Install dependencies:

    $ npm install
  2. Run the application.

$ npm start
  1. Check it out at http://localhost:3000

That's it!

Meta

  • No warranty expressed or implied. Software is as is. Diggity.
  • MIT License
  • Lovingly crafted by Twilio Developer Education.
    • Then Migrated by Bandwidth Developer Education.