Skip to content
This repository was archived by the owner on May 22, 2023. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f429ea6
Activate virtualenv before running server.
Jun 2, 2021
2401078
Add more explanation for example .env file.
Jun 3, 2021
4acbc11
Add node_modules to gitignore.
Jun 3, 2021
634ecef
Fix token decoding error.
Jun 3, 2021
5a92852
Remove CDN and use npm to install Twilio voice sdk.
Jun 3, 2021
39f684b
Remove reference to deprecated capability tokens.
Jun 3, 2021
32099c1
Remove Device options that are now defaults in voice-sdk 2.0.
Jun 3, 2021
917ec47
Stop listening for device.on(ready) and instead listen for registered.
Jun 3, 2021
3354df0
Move connect and disconnect events to Call object instead of Device o…
Jun 3, 2021
419021c
Rename connect objects to call objects.
Jun 4, 2021
cde7935
Pass device to updateDevices functions.
Jun 4, 2021
1481de4
Change param key for outbound call payload.
Jun 4, 2021
0acbd79
Allow for incoming calls, and change payload from to to phone.
Jun 4, 2021
e2996fe
Add npm install step to install command.
Jun 4, 2021
e195eb1
Update README with new install steps.
Jun 4, 2021
9a10e40
Handle incoming calls in the UI.
Jun 4, 2021
88d1b43
Simplify if statement for outbound calls.
Jun 4, 2021
697a05f
Fix tests.
Jun 4, 2021
2937994
Remove redundant check in if condition.
Jun 4, 2021
a6e644d
Show accept/reject incoming call buttons on incoming call.
Jun 4, 2021
df760ce
Add screenshots and additional instructions to README.
Jun 4, 2021
4eb2c16
Use let/const instead of var.
Jun 7, 2021
9d12f18
Simplify js code.
Jun 7, 2021
b088654
Fix incorrect var initialization.
Jun 8, 2021
926fe6e
Use more canonical JS for creating new arrays.
Jun 8, 2021
0857657
Clarify comments in .env.example.
Jun 8, 2021
c29a4b9
Fix assignment to a constant error.
Jun 10, 2021
267a76b
Lint python code.
Jun 23, 2021
5566757
Pull in changes to frontend code from node quickstart.
Jun 23, 2021
cab32d0
Remove colon from index.html check.
Jun 23, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# The Twilio number for making outbound and receiving inbound calls
# Purchase a Twilio phone number in the console
# https://www.twilio.com/console/phone-numbers/search
TWILIO_CALLER_ID=+1XXXYYYZZZZ

# SID of your TwiML Application
# Create a new TwiML app in the console
# https://www.twilio.com/console/voice/twiml/apps
# OR
# OR with the Twilio CLI
# twilio api:core:applications:create --friendly-name=voice-client-javascript
TWILIO_TWIML_APP_SID=APXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# Your REST API Key information
# Create a new key in the console and copy the SID and secret.
# https://www.twilio.com/console/project/api-keys
# OR
# twilio api:core:keys:create --friendly-name=voice-client-javascript -o json
# NOTE: Make sure to copy the secret, it'll will only be displayed once
API_KEY=SKXXXXXXXXXXXX
# NOTE: Make sure to copy the secret, it will only be displayed once
API_KEY=SKXXXXXXXXXXXX # the SID of the key
API_SECRET=XXXXXXXXXXXXXX
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ venv
__pycache__
.pytest_cache
.tool-versions
node_modules/
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ else
. venv/bin/activate; \
pip3 install -r requirements.txt;
endif
cd static && npm install

serve:
ifeq ($(UNAME), Windows)
venv\Scripts\activate.bat; \
python3 app.py
else
. venv/bin/activate; \
python3 app.py
endif
38 changes: 26 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

## About

This application should give you a ready-made starting point for writing your own voice apps with Twilio Client. This application uses the lightweight [Flask Framework](http://flask.pocoo.org/).
This application should give you a ready-made starting point for writing your own voice apps with Twilio Client. This application uses the lightweight [Flask Framework](http://flask.pocoo.org/). Once you set up the application, you will be able to make and receive calls from your browser. You will also be able to switch between audio input/output devices, and see dynamic volume levels on the call.

![screenshot of application homepage](./screenshot_on_call.png)

Implementations in other languages:

Expand All @@ -23,6 +25,8 @@ Implementations in other languages:
### Requirements

- [Python](https://www.python.org/) **3.6**, **3.7** or **3.8** version.
- [Node](https://nodejs.org/en/), version **14.0** or above.
- [ngrok](https://ngrok.com/download)

### Twilio Account Settings

Expand All @@ -44,13 +48,13 @@ Before we begin, we need to collect all the config values we need to run the app
cd client-quickstart-python
```

2. Create the virtual environment, load it and install the dependencies.
2. Run `make install`. This command will create a Python virtual environment, load it, and install the Python dependencies. It will also download the `@twilio/voice-sdk` npm package and create a `node_modules` directory inside the `static` directory.

```bash
make install
```

3. Create a configuration file for your application and edit the `.env` file.
3. Create a configuration file for your application by copying the `.env.example` file to a new file called `.env`. Then, edit the `.env` file to include your account and application details.

```bash
cp .env.example .env
Expand All @@ -75,15 +79,15 @@ Before we begin, we need to collect all the config values we need to run the app
After re-running the script, the environment variables will be peramently set for
your user account.

4. Run the application, will run on port 5000.
4. Run the application. It will run locally on port 5000.

```bash
make serve
```

5. Navigate to [http://localhost:5000](http://localhost:5000)

6. Expose your application to the wider internet using [ngrok](https://ngrok.com/download). You can click [here](https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html) for more details. This step **is important** because the application won't work as expected if you run it through localhost.
6. Expose your application to the wider internet using [ngrok](https://ngrok.com/download). You can click [here](https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html) for more details. This step **is important** and your application won't work if you only run the server on localhost.

```bash
ngrok http 5000
Expand All @@ -98,14 +102,23 @@ Voice "REQUEST URL" to be your ngrok URL plus `/voice`. For example:
![screenshot of twiml app](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/TwilioClientRequestUrl.original.png)

> **Note:** You must set your webhook urls to the `https` ngrok tunnel created.
You should now be ready to rock! Make some phone calls.
Open it on another device and call yourself. Note that Twilio Client requires
WebRTC enabled browsers, so Edge and Internet Explorer will not work for testing.
We'd recommend Google Chrome or Mozilla Firefox instead.

![screenshot of phone app](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/TwilioClientQuickstart.original.png)
You should now be ready to rock! Make some phone calls or receiving incoming calls in the application.
Note that Twilio Client requires WebRTC enabled browsers, so Edge and Internet Explorer will not work for testing.
We'd recommend Google Chrome or Mozilla Firefox instead.

![screenshot of application homepage](./screenshot_homepage.png)

When the server starts, you will be assigned a random client name, which will appear in the top left corner of the homepage. This client name is used as the identity field when generating an access token for the client, and is also used to route incoming calls to the correct client device.

You can make outbound calls by entering a phone number or a client name. If you would like to test browser-to-browser calls, open one browser page to `localhost:5000` and then stop and restart the server, which will generate a new client identity. Open a new browser to `localhost:5000`, and you should see the new client name. You can make calls between these two clients by entering one client's name in the box for making an outbound call.

![screenshot of application homepage](./screenshot_two_calls.png)

You can also receiving an incoming call to your browser by calling the Twilio number you specified as your `TWILIO_CALLER_ID` in your `.env` file.

That's it!
If you see "Unknown Audio Output Device 1" in the "Ringtone" or "Speaker" devices lists, click the button below the boxes (Seeing "Unknown" Devices?) to have your browser identify your input and output devices.
![screenshot of unknown devices](./screenshot_unknown_devices.png)

### Docker

Expand All @@ -121,7 +134,8 @@ If you have [Docker](https://www.docker.com/) already installed on your machine,
You can run the tests locally with the following command. Before running, make sure the virtual environment is activated.

```bash
$ python3 -m pytest
source venv/bin/activate
python3 -m pytest
```

### Cloud deployment
Expand Down
67 changes: 40 additions & 27 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,40 @@

import os
import re
from flask import Flask, jsonify, request, Response

from dotenv import load_dotenv
from faker import Faker
from flask import Flask, Response, jsonify, redirect, request
from twilio.jwt.access_token import AccessToken
from twilio.jwt.access_token.grants import VoiceGrant
from twilio.twiml.voice_response import VoiceResponse, Dial
from twilio.twiml.voice_response import Dial, VoiceResponse

from dotenv import load_dotenv
load_dotenv()

app = Flask(__name__)
fake = Faker()
alphanumeric_only = re.compile('[\W_]+')
alphanumeric_only = re.compile("[\W_]+")
phone_pattern = re.compile(r"^[\d\+\-\(\) ]+$")

twilio_number = os.environ["TWILIO_CALLER_ID"]

# Generate a random user name
identity = alphanumeric_only.sub("", fake.user_name())


@app.route('/')
@app.route("/")
def index():
return app.send_static_file('index.html')
return app.send_static_file("index.html")


@app.route('/token', methods=['GET'])
@app.route("/token", methods=["GET"])
def token():
# get credentials for environment variables
account_sid = os.environ['TWILIO_ACCOUNT_SID']
application_sid = os.environ['TWILIO_TWIML_APP_SID']
api_key = os.environ['API_KEY']
api_secret = os.environ['API_SECRET']

# Generate a random user name
identity = alphanumeric_only.sub('', fake.user_name())

account_sid = os.environ["TWILIO_ACCOUNT_SID"]
application_sid = os.environ["TWILIO_TWIML_APP_SID"]
api_key = os.environ["API_KEY"]
api_secret = os.environ["API_SECRET"]

# Create access token with credentials
token = AccessToken(account_sid, api_key, api_secret, identity=identity)

Expand All @@ -44,30 +47,40 @@ def token():
token.add_grant(voice_grant)

# Return token info as JSON
token=token.to_jwt()

token = token.to_jwt()

# Return token info as JSON
return jsonify(identity=identity, token=token.decode('utf-8'))
return jsonify(identity=identity, token=token)


@app.route("/voice", methods=['POST'])
@app.route("/voice", methods=["POST"])
def voice():
resp = VoiceResponse()
if "To" in request.form and request.form["To"] != '':
dial = Dial(caller_id=os.environ['TWILIO_CALLER_ID'])
if request.form.get("To") == twilio_number:
# Receiving an incoming call to our Twilio number
dial = Dial()
dial.client(identity)
resp.append(dial)
elif request.form.get("phone"):
# Placing an outbound call from the Twilio client
dial = Dial(caller_id=twilio_number)
# wrap the phone number or client name in the appropriate TwiML verb
# by checking if the number given has only digits and format symbols
if phone_pattern.match(request.form["To"]):
dial.number(request.form["To"])
if phone_pattern.match(request.form["phone"]):
dial.number(request.form["phone"])
else:
dial.client(request.form["To"])
dial.client(request.form["phone"])
resp.append(dial)
else:
resp.say("Thanks for calling!")

return Response(str(resp), mimetype='text/xml')
return Response(str(resp), mimetype="text/xml")


@app.route("/static/twilio.min.js")
def static_files():
return redirect("/static/node_modules/@twilio/voice-sdk/dist/twilio.min.js")


if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0")
Binary file added screenshot_homepage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshot_on_call.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshot_two_calls.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshot_unknown_devices.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
95 changes: 60 additions & 35 deletions static/index.html
Original file line number Diff line number Diff line change
@@ -1,39 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<title>Twilio Client Quickstart</title>
<link rel="stylesheet" href="/static/site.css">
</head>
<body>
<div id="controls">
<div id="info">
<p class="instructions">Twilio Client</p>
<div id="client-name"></div>
<div id="output-selection">
<label>Ringtone Devices</label>
<select id="ringtone-devices" multiple></select>
<label>Speaker Devices</label>
<select id="speaker-devices" multiple></select><br/>
<a id="get-devices">Seeing unknown devices?</a>
</div>
</div>
<div id="call-controls">
<p class="instructions">Make a Call:</p>
<input id="phone-number" type="text" placeholder="Enter a phone # or client name" />
<button id="button-call">Call</button>
<button id="button-hangup">Hangup</button>
<div id="volume-indicators">
<label>Mic Volume</label>
<div id="input-volume"></div><br/><br/>
<label>Speaker Volume</label>
<div id="output-volume"></div>
</div>
</div>
<div id="log"></div>
</div>
<head>
<title>Twilio Voice JavaScript SDK Quickstart</title>
<link rel="stylesheet" href="static/site.css" />
</head>
<body>
<header>
<h1>Twilio Voice JavaScript SDK Quickstart</h1>
<button id="startup-button">Start up the Device</button>
</header>
<main id="controls">
<section class="left-column" id="info">
<h2>Your Device Info</h2>
<div id="client-name"></div>
<div id="output-selection" class="hide">
<label>Ringtone Devices</label>
<select id="ringtone-devices" multiple></select>
<label>Speaker Devices</label>
<select id="speaker-devices" multiple></select
><br />
<button id="get-devices">Seeing "Unknown" devices?</button>
</div>
</section>
<section class="center-column">
<h2 class="instructions">Make a Call</h2>
<div id="call-controls" class="hide">
<form>
<label for="phone-number"
>Enter a phone number or client name</label
>
<input id="phone-number" type="text" placeholder="+15552221234" />
<button id="button-call" type="submit">Call</button>
</form>
<button id="button-hangup" class="hide">Hang Up</button>
<div id="incoming-call" class="hide">
<h2>Incoming Call Controls</h2>
<p class="instructions">
Incoming Call from <span id="incoming-number"></span>
</p>
<button id="button-accept-incoming">Accept</button>
<button id="button-reject-incoming">Reject</button>
<button id="button-hangup-incoming" class="hide">Hangup</button>
</div>
<div id="volume-indicators" class="hide">
<label>Mic Volume</label>
<div id="input-volume"></div>
<br /><br />
<label>Speaker Volume</label>
<div id="output-volume"></div>
</div>
</div>
</section>
<section class="right-column">
<h2>Event Log</h2>
<div class="hide" id="log"></div>
</section>
</main>

<script type="text/javascript" src="//sdk.twilio.com/js/client/releases/1.10.1/twilio.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="/static/quickstart.js"></script>
</body>
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script type="text/javascript" src="static/twilio.min.js"></script>
<script src="static/quickstart.js"></script>
</body>
</html>
Loading