Skip to content

Commit 5723ef9

Browse files
committed
Resolve yarn.lock conflict
2 parents b9373f0 + ec1f9f0 commit 5723ef9

File tree

6 files changed

+182
-48
lines changed

6 files changed

+182
-48
lines changed

.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ CALL_ANNOUCEMENT_VOICE=
55
CALL_ANNOUCEMENT_LANGUAGE=
66
OUT_OF_SESSION_MESSAGE_FOR_CALL=
77
CONNECTING_CALL_ANNOUCEMENT=
8-
DOMAIN=
8+
DOMAIN=
9+
AUTH_USERNAME=
10+
AUTH_PASSWORD=

README.md

Lines changed: 146 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,154 @@
1+
2+
# Masked Communications App
13
[![Tests](https://github.com/aymenn/masked-comms-app/actions/workflows/test.yml/badge.svg)](https://github.com/aymenn/masked-comms-app/actions/workflows/test.yml)
24

3-
# About
4-
This is an example masked communicaiton application that demonstrates how build it by using [Conversations API](https://twilio.com/docs/conversations) and [Programmable Voice](https://twilio.com/docs/voice).
5+
This is an open-source version of [Twilio Proxy](https://www.twilio.com/docs/proxy), built on top of the [Twilio Conversations API](https://www.twilio.com/docs/conversations). It adds the following features to conversations:
6+
7+
- 🤿 Add 2-50 SMS participants to a conversation with masked numbers
8+
- 🔀 Automatic proxy number selection from a number pool
9+
- ☎️ Supports 1:1 and conference calling via the Conversations proxy number.
510

6-
This application supports masking communicaitons between SMS, WhatsApp, and voice participants
11+
Reasons you might use this app:
12+
- 🏠 You're a real estate company that wants to connect realtors with prospective buyers over a masked number.
13+
- 🚗 You're a rideshare service that wants to give riders a temporary number to call their driver.
14+
- 🎁 You're a personal shopper platform that wants to connect shoppers with customers over a private number and log all conversation messages.
15+
16+
Using the underlying Twilio Conversations platform, you also have the capability to do things like:
17+
- 🚫 Block messages from going out based on their content.
18+
- 🗃 Store messages to a database for analysis.
19+
- 🏁 Receive webhooks from the Conversations event framework.
720

821
# Prerequisites
9-
- Twilio Account
10-
- 1 or more Twilio phone numbers (either bought or verified) to mask SMS and voice communications
22+
- A Twilio Account, you can get a free one [by signing up here](https://twilio.com/try-twilio)
23+
- 1 or [more](https://www.twilio.com/docs/proxy/phone-numbers-needed) Twilio phone numbers to mask SMS and voice communications
1124
- Node.js v14 or higher
12-
- ngrok if running a local server
25+
- Yarn or NPM
26+
- If you're running the app locally, you'll need a tool like [ngrok](https://ngrok.com/) so that Twilio can send webhooks to your app.
1327

1428
# Getting Started
15-
1. Copy .env.template to .env
16-
2. Set your account sid and auth token in the corresponding variables
17-
3. Set NUMBER_POOL to an array of phone number(s) e.g.
18-
> NUMBER_POOL=["+19255551111","+19257775555"]
19-
1. If using ngrok, start ngrok for port 9000
20-
> ngrok http 9000
21-
5. Configure your phone number's inbound call handler with the url *https://your_ngrok_server/proxypoc/inboundCall*
22-
6. Install the app dependency packages
23-
> npm install
24-
7. Start your npm server
25-
> npm start
26-
27-
# Using
28-
This application provides an endpoint to create a conversation for participants to communicate and a landing page to simplify getting started.
29-
30-
1. Navigate your browser to *http://localhost:9000/*
31-
2. You will see the configured phone numbers and a section titled *Create a new proxy session*
32-
3. Enter the numbers of two participant that you wish to connect anonymously
33-
4. Click *Submit*
34-
5. A JSON string is presented which contains an array of the participants added to the conversation. Each participant will have a messaging_binding.proxy_address. This is the address that the participants will send and receive numbers to and from
35-
6. Have participant A send a message to their proxy_number
36-
7. Participant B should receive the message from the Participant A's proxy_number
37-
8. You can also have participant A call the proxy_number. This should set up a call to Participant B and participant B should see Participant A's proxy phone number
38-
39-
# Endpoint
40-
## /sessions
41-
The sessions endpoint accepts one or more addresses. It creates a Conversation, finds a Proxy number for each participant and adds them to the conversation
42-
43-
## /inboundCall
44-
This endpoint receives inbound call events. It will query Twilio Conversations for a matching participant address and connect the other participant to the caller
45-
46-
# Number Choosing
47-
The number chooser included is a simple algorithm that picks a number from NUMBER_POOL that isnt used by the given participant's address.
29+
Begin by cloning the repository, installing dependencies, and setting environment variables:
30+
31+
```bash
32+
# Clone the repository:
33+
$ git clone [email protected]:aymenn/masked-comms-app.git
34+
35+
# Make the repository your working directory:
36+
$ cd masked-comms-app
37+
38+
# Install dependencies:
39+
$ yarn install
40+
41+
# Copy the example envrionment variable file:
42+
$ cp .env.example .env
43+
```
44+
45+
| Variable Name | Description | Example |
46+
|---------------------------------|-------------------------------------------------------------------------------|------------------------------------------------------------|
47+
| TWILIO_ACCOUNT_SID | The identifier for your Twilio Account. | ACXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
48+
| TWILIO_AUTH_TOKEN | The auth token for accessing the Twilio API. | ****************************** |
49+
| NUMBER_POOL | An array of phone numbers to use as your number pool in e164 format. | ["+141512345678", "+14230509876"] |
50+
| CALL_ANNOUCEMENT_VOICE | The type of voice to use for speech to text announcements. | "man", "woman", "alice", or any of the [Amazon Polly voices](https://www.twilio.com/docs/voice/twiml/say/text-speech#polly-standard-and-neural-voices). |
51+
| CALL_ANNOUCEMENT_LANGUAGE | The language to speak announcements in. | "en" or any of the [supported languages](https://www.twilio.com/docs/voice/twiml/say#attributes-language). |
52+
| OUT_OF_SESSION_MESSAGE_FOR_CALL | A message to play if someone calls the number pool without an active session. | "Your session is no longer active. Goodbye." |
53+
| CONNECTING_CALL_ANNOUCEMENT | A message to play when a caller is being connected to the other party. | "We're connecting you to your agent now." |
54+
| DOMAIN | The domain where the application will be hosted. | "mysite.com" or "your-domain.ngrok.io" (no https://) |
55+
| AUTH_USERNAME | Basic auth username for request authorization | "mySecureUserName" |
56+
| AUTH_PASSWORD | Basic auth password for request authorization | "mySecretPassword" |
57+
58+
Once you have your environment variables set, you can start the app with this command:
59+
60+
```bash
61+
$ yarn dev
62+
63+
# or
64+
65+
$ npm run dev
66+
```
67+
68+
To open a tunnel to your localhost that Twilio can send webhooks to, you can use ngrok:
69+
70+
```bash
71+
$ ngrok http 3000
72+
```
73+
74+
# Configuring Webhooks
75+
Two webhooks can be configured in the Twilio Console:
76+
77+
1. Incoming call webhook: receives a request whenever a user makes an inbound call and connects them to the right people.
78+
- Go to Twilio Console > Phone Numbers > Manage > Active Numbers > Click on the number to configure.
79+
- Scroll down to the "Voice & Fax" configuration section to "A Call Comes In".
80+
- Select "Webhook" from the dropdown and paste in your webhook: `https://[your-domain]/inbound-call`.
81+
82+
2. Conversation post-event webhook: this webhook can receive "conversation closed" events from Twilio Conversations and automatically delete the closed conversation. Keeping the pool of conversations small improves app performance and reduces cost.
83+
84+
- Go to Twilio Console > Conversations > Manage > Services > Your Service > Webhooks.
85+
- Check the `onConversationStateUpdated` box.
86+
- Paste your webhook (`https://[your-domain]/conversations-post-event`) into the Post-Event URL input box.
87+
- Click "save" at the bottom of the page.
88+
89+
# Authentication & Webhook Validation
90+
The app requires basic auth on request to the `/sessions` endpoint. This prevents an unauthorized person from creating sessions. To use basic auth, make sure `DOMAIN` (e.g. mysite.com, no http://), `AUTH_USERNAME`, and `AUTH_PASSWORD` are all set in your .env file, and restart the app.
91+
92+
Webhooks are automatically validated using the Twilio Webhook signature. This prevents an unauthorized request to start a phone call without your permission. For webhook validation to work, your app needs `DOMAIN` to be set along with `TWILIO_AUTH_TOKEN` in the .env file.
93+
94+
# Usage
95+
You can create a new masked-number session between multiple users by making a post request to the `/sessions` endpoint:
96+
97+
```bash
98+
curl --location --request POST 'localhost:3000/sessions' \
99+
--header 'Authorization: Basic 123XYZ==' \
100+
--header 'Content-Type: application/json' \
101+
--data-raw '{
102+
"addresses": [
103+
"+1234567890",
104+
"+0123456789"
105+
],
106+
"friendlyName": "My First Conversation"
107+
}'
108+
```
109+
- The app supports basic auth, which can be configured in the .env file.
110+
- Addresses is an array of e164 formatted phone numbers. You can add between 1-50 participants to a conversation at a time.
111+
- The app also accepts all [conversations CREATE properties](https://www.twilio.com/docs/conversations/api/conversation-resource#conversation-properties), e.g. `friendlyName`.
112+
113+
The app will respond with the JSON from a typical Create Converation API call:
114+
115+
```json
116+
{
117+
"accountSid": "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
118+
"chatServiceSid": "ISXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
119+
"messagingServiceSid": "MGXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
120+
"sid": "CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
121+
"friendlyName": "My First Conversation",
122+
"uniqueName": null,
123+
"attributes": "{}",
124+
"state": "active",
125+
"dateCreated": "2022-07-22T05:41:18.000Z",
126+
"dateUpdated": "2022-07-22T05:41:18.000Z",
127+
"timers": {},
128+
"url": "https://conversations.twilio.com/v1/Conversations/CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
129+
"links": {
130+
"participants": "https://conversations.twilio.com/v1/Conversations/CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Participants",
131+
"messages": "https://conversations.twilio.com/v1/Conversations/CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Messages",
132+
"webhooks": "https://conversations.twilio.com/v1/Conversations/CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Webhooks"
133+
},
134+
"bindings": null
135+
}
136+
```
137+
138+
# Errors and Retries
139+
- The app will retry requests to Twilio when it recieves a `429 - Too many requests` error code. You can configure the retry behavior in `src/config/retry.config.ts`.
140+
141+
- If you don't have enough phone numbers in your number pool, you'll receive a 500 response with the message `Not enough numbers available in pool for [phone_number]`.
142+
143+
# Running Tests
144+
To execute unit tests, run:
145+
146+
```bash
147+
$ yarn test
148+
```
149+
150+
To conduct a load test on the app, run:
151+
```bash
152+
$ yarn loadtest
153+
```
154+
This will generate 300 conversations in 20ms intervals against the app.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"cors": "^2.8.5",
66
"dotenv": "^16.0.1",
77
"express": "^4.18.1",
8+
"express-basic-auth": "^1.2.1",
89
"http-errors": "^2.0.0",
910
"morgan": "^1.10.0",
1011
"twilio": "^3.78.0"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function shouldValidate () {
2+
return process.env.NODE_ENV !== 'test'
3+
}
4+
5+
export const webhookConfig = {
6+
protocol: 'https',
7+
host: process.env.DOMAIN,
8+
validate: shouldValidate()
9+
}

src/routes/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
import { Router } from "express";
22
import * as controllers from "../controllers";
33

4+
import twilio from 'twilio'
5+
import { webhookConfig } from '../config/webhookValidationConfig'
6+
7+
import basicAuth from 'express-basic-auth'
8+
const { AUTH_USERNAME, AUTH_PASSWORD } = process.env
9+
410
const router = Router();
511

6-
router.post("/conversations-post-event", controllers.conversationsPostEvent.post);
12+
router.post("/conversations-post-event", twilio.webhook(webhookConfig), controllers.conversationsPostEvent.post);
713

8-
router.post("/inbound-call", controllers.inboundCall.post);
9-
router.get("/join-conference", controllers.joinConference.get)
14+
router.post("/inbound-call", twilio.webhook(webhookConfig), controllers.inboundCall.post);
15+
router.post("/join-conference", twilio.webhook(webhookConfig), controllers.joinConference.get)
1016

11-
router.post("/sessions", controllers.session.post);
17+
router.post("/sessions", basicAuth({
18+
users: { [AUTH_USERNAME]:AUTH_PASSWORD}
19+
}), controllers.session.post);
1220

1321
export default router;

yarn.lock

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ balanced-match@^1.0.0:
937937
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
938938
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
939939

940-
basic-auth@~2.0.1:
940+
basic-auth@^2.0.1, basic-auth@~2.0.1:
941941
version "2.0.1"
942942
resolved "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz"
943943
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
@@ -1274,9 +1274,9 @@ [email protected]:
12741274
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
12751275

12761276
electron-to-chromium@^1.4.188:
1277-
version "1.4.194"
1278-
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.194.tgz#2f83fcec5067907044a3d502ac7c3efb1fe6430b"
1279-
integrity sha512-ola5UH0xAP1oYY0FFUsPvwtucEzCQHucXnT7PQ1zjHJMccZhCDktEugI++JUR3YuIs7Ff7afz+OVEhVAIMhLAQ==
1277+
version "1.4.195"
1278+
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.195.tgz#139b2d95a42a3f17df217589723a1deac71d1473"
1279+
integrity sha512-vefjEh0sk871xNmR5whJf9TEngX+KTKS3hOHpjoMpauKkwlGwtMz1H8IaIjAT/GNnX0TbGwAdmVoXCAzXf+PPg==
12801280

12811281
emittery@^0.10.2:
12821282
version "0.10.2"
@@ -1361,6 +1361,13 @@ expect@^28.1.3:
13611361
jest-message-util "^28.1.3"
13621362
jest-util "^28.1.3"
13631363

1364+
express-basic-auth@^1.2.1:
1365+
version "1.2.1"
1366+
resolved "https://registry.yarnpkg.com/express-basic-auth/-/express-basic-auth-1.2.1.tgz#d31241c03a915dd55db7e5285573049cfcc36381"
1367+
integrity sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==
1368+
dependencies:
1369+
basic-auth "^2.0.1"
1370+
13641371
express@^4.18.1:
13651372
version "4.18.1"
13661373
resolved "https://registry.npmjs.org/express/-/express-4.18.1.tgz"

0 commit comments

Comments
 (0)