Initial commit of Twilio Server Starter for Python
This commit is contained in:
18
.env.example
Normal file
18
.env.example
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Required for all uses
|
||||||
|
TWILIO_ACCOUNT_SID=
|
||||||
|
TWILIO_API_KEY=
|
||||||
|
TWILIO_API_SECRET=
|
||||||
|
|
||||||
|
# Required for Video
|
||||||
|
TWILIO_CONFIGURATION_SID=
|
||||||
|
|
||||||
|
# Required for IP Messaging
|
||||||
|
TWILIO_IPM_SERVICE_SID=
|
||||||
|
|
||||||
|
# Required for Notify
|
||||||
|
TWILIO_NOTIFICATION_SERVICE_SID=
|
||||||
|
TWILIO_APN_CREDENTIAL_SID=
|
||||||
|
TWILIO_GCM_CREDENTIAL_SID=
|
||||||
|
|
||||||
|
# Required for Sync
|
||||||
|
TWILIO_SYNC_SERVICE_SID=
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
.env
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
|||||||
94
README.md
94
README.md
@@ -1,2 +1,92 @@
|
|||||||
# sdk-starter-python
|
# Twilio SDK Starter Application for Python
|
||||||
Demo application showcasing Twilio API usage in Python
|
|
||||||
|
This sample project demonstrates how to use Twilio APIs in a Python web
|
||||||
|
application. Once the app is up and running, check out [the home page](http://localhost:5000)
|
||||||
|
to see which demos you can run. You'll find examples for [IP Messaging](https://www.twilio.com/ip-messaging),
|
||||||
|
[Video](https://www.twilio.com/video), [Sync](https://www.twilio.com/sync), and more.
|
||||||
|
|
||||||
|
Let's get started!
|
||||||
|
|
||||||
|
## Configure the sample application
|
||||||
|
|
||||||
|
To run the application, you'll need to gather your Twilio account credentials and configure them
|
||||||
|
in a file named `.env`. To create this file from an example template, do the following in your
|
||||||
|
Terminal.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `.env` in your favorite text editor and configure the following values.
|
||||||
|
|
||||||
|
### Configure account information
|
||||||
|
|
||||||
|
Every sample in the demo requires some basic credentials from your Twilio account. Configure these first.
|
||||||
|
|
||||||
|
| Config Value | Description |
|
||||||
|
| :------------- |:------------- |
|
||||||
|
`TWILIO_ACCOUNT_SID` | Your primary Twilio account identifier - find this [in the console here](https://www.twilio.com/console).
|
||||||
|
`TWILIO_API_KEY` | Used to authenticate - [generate one here](https://www.twilio.com/console/video/dev-tools/api-keys).
|
||||||
|
`TWILIO_API_SECRET` | Used to authenticate - [just like the above, you'll get one here](https://www.twilio.com/console/video/dev-tools/api-keys).
|
||||||
|
|
||||||
|
#### A Note on API Keys
|
||||||
|
|
||||||
|
When you generate an API key pair at the URLs above, your API Secret will only be shown once -
|
||||||
|
make sure to save this information in a secure location, or possibly your `~/.bash_profile`.
|
||||||
|
|
||||||
|
### Configure product-specific settings
|
||||||
|
|
||||||
|
Depending on which demos you'd like to run, you'll need to configure a few more values in your
|
||||||
|
`.env` file.
|
||||||
|
|
||||||
|
| Config Value | Product Demo | Description |
|
||||||
|
| :------------- |:------------- |:------------- |
|
||||||
|
`TWILIO_IPM_SERVICE_SID` | IP Messaging | Like a database for your IP Messaging data - [generate one in the console here](https://www.twilio.com/console/ip-messaging/services)
|
||||||
|
`TWILIO_CONFIGURATION_SID` | Video | Identifier for a set of config properties for your video application - [find yours here](https://www.twilio.com/console/video/profiles)
|
||||||
|
`TWILIO_SYNC_SERVICE_SID` | Sync (Preview) | Like a database for your Sync data - generate one with the curl command below.
|
||||||
|
`TWILIO_NOTIFICATION_SERVICE_SID` | Notify (Preview) | You will need to create a Notify service - [generate one here](https://www.twilio.com/console/notify/services)
|
||||||
|
`TWILIO_APN_CREDENTIAL_SID` | Notify (Preview) | Adds iOS notification ability to your app - [generate one here](https://www.twilio.com/console/notify/credentials). You'll need to provision your APN push credentials to generate this. See [this](https://www.twilio.com/docs/api/ip-messaging/guides/push-notifications-ios) guide on how to do that. (Optional)
|
||||||
|
`TWILIO_GCM_CREDENTIAL_SID` | Notify (Preview) |Adds Android/GCM notification ability to your app - [generate one here](https://www.twilio.com/console/notify/credentials). You'll need to provision your GCM push credentials to generate this. See [this](https://www.twilio.com/docs/api/ip-messaging/guides/push-notifications-android) guide on how to do that (Optional)
|
||||||
|
|
||||||
|
#### Temporary: Generating a Sync Service Instance
|
||||||
|
|
||||||
|
During the Sync developer preview, you will need to generate Sync service
|
||||||
|
instances via API until the Console GUI is available. Using the API key pair you
|
||||||
|
generated above, generate a service instance via REST API with this curl command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST https://preview.twilio.com/Sync/Services \
|
||||||
|
-d 'FriendlyName=MySyncServiceInstance' \
|
||||||
|
-u 'SKXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:your_api_secret'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run the sample application
|
||||||
|
|
||||||
|
This application uses the lightweight [Flask Framework](http://flask.pocoo.org/).
|
||||||
|
|
||||||
|
We need to set up your Python environment. Install `virtualenv` via `pip`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install virtualenv
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, we need to install our depenedencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
virtualenv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we should be all set! Run the application using the `python` command.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Your application should now be running at [http://localhost:5000](http://localhost:5000). When you're finished, deactivate your virtual environment using `deactivate`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## License
|
||||||
|
MIT
|
||||||
148
app.py
Normal file
148
app.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import os
|
||||||
|
from flask import Flask, jsonify, request
|
||||||
|
from faker import Factory
|
||||||
|
from twilio.rest import Client
|
||||||
|
from twilio.jwt.access_token import AccessToken, SyncGrant, ConversationsGrant, IpMessagingGrant
|
||||||
|
from dotenv import load_dotenv, find_dotenv
|
||||||
|
from os.path import join, dirname
|
||||||
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
fake = Factory.create()
|
||||||
|
dotenv_path = join(dirname(__file__), '.env')
|
||||||
|
load_dotenv(dotenv_path)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return app.send_static_file('index.html')
|
||||||
|
|
||||||
|
@app.route('/video/')
|
||||||
|
def video():
|
||||||
|
return app.send_static_file('video/index.html')
|
||||||
|
|
||||||
|
@app.route('/sync/')
|
||||||
|
def sync():
|
||||||
|
return app.send_static_file('sync/index.html')
|
||||||
|
|
||||||
|
@app.route('/notify/')
|
||||||
|
def notify():
|
||||||
|
return app.send_static_file('notify/index.html')
|
||||||
|
|
||||||
|
@app.route('/ipmessaging/')
|
||||||
|
def ipmessaging():
|
||||||
|
return app.send_static_file('ipmessaging/index.html')
|
||||||
|
|
||||||
|
# Basic health check - check environment variables have been configured
|
||||||
|
# correctly
|
||||||
|
@app.route('/config')
|
||||||
|
def config():
|
||||||
|
return jsonify(
|
||||||
|
TWILIO_ACCOUNT_SID=os.environ['TWILIO_ACCOUNT_SID'],
|
||||||
|
TWILIO_NOTIFICATION_SERVICE_SID=os.environ['TWILIO_NOTIFICATION_SERVICE_SID'],
|
||||||
|
TWILIO_APN_CREDENTIAL_SID=os.environ['TWILIO_APN_CREDENTIAL_SID'],
|
||||||
|
TWILIO_GCM_CREDENTIAL_SID=os.environ['TWILIO_GCM_CREDENTIAL_SID'],
|
||||||
|
TWILIO_API_KEY=os.environ['TWILIO_API_KEY'],
|
||||||
|
TWILIO_API_SECRET=bool(os.environ['TWILIO_API_SECRET']),
|
||||||
|
TWILIO_IPM_SERVICE_SID=os.environ['TWILIO_IPM_SERVICE_SID'],
|
||||||
|
TWILIO_SYNC_SERVICE_SID=os.environ['TWILIO_SYNC_SERVICE_SID'],
|
||||||
|
TWILIO_CONFIGURATION_SID=os.environ['TWILIO_CONFIGURATION_SID']
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route('/token')
|
||||||
|
def token():
|
||||||
|
# get credentials for environment variables
|
||||||
|
account_sid = os.environ['TWILIO_ACCOUNT_SID']
|
||||||
|
api_key = os.environ['TWILIO_API_KEY']
|
||||||
|
api_secret = os.environ['TWILIO_API_SECRET']
|
||||||
|
sync_service_sid = os.environ['TWILIO_SYNC_SERVICE_SID']
|
||||||
|
configuration_profile_sid = os.environ['TWILIO_CONFIGURATION_SID']
|
||||||
|
ipm_service_sid = os.environ['TWILIO_IPM_SERVICE_SID']
|
||||||
|
|
||||||
|
|
||||||
|
# create a randomly generated username for the client
|
||||||
|
identity = fake.user_name()
|
||||||
|
|
||||||
|
# Create a unique endpoint ID for the
|
||||||
|
device_id = request.args.get('device')
|
||||||
|
endpoint = "TwilioAppDemo:{0}:{1}".format(identity, device_id)
|
||||||
|
|
||||||
|
# Create access token with credentials
|
||||||
|
token = AccessToken(account_sid, api_key, api_secret, identity)
|
||||||
|
|
||||||
|
# Create a Sync grant and add to token
|
||||||
|
if sync_service_sid:
|
||||||
|
sync_grant = SyncGrant(endpoint_id=endpoint, service_sid=sync_service_sid)
|
||||||
|
token.add_grant(sync_grant)
|
||||||
|
|
||||||
|
# Create a Video grant and add to token
|
||||||
|
if configuration_profile_sid:
|
||||||
|
video_grant = ConversationsGrant(configuration_profile_sid=configuration_profile_sid)
|
||||||
|
token.add_grant(video_grant)
|
||||||
|
|
||||||
|
# Create an IP Messaging grant and add to token
|
||||||
|
if ipm_service_sid:
|
||||||
|
ipm_grant = IpMessagingGrant(endpoint_id=endpoint, service_sid=ipm_service_sid)
|
||||||
|
token.add_grant(ipm_grant)
|
||||||
|
|
||||||
|
# Return token info as JSON
|
||||||
|
return jsonify(identity=identity, token=token.to_jwt())
|
||||||
|
|
||||||
|
# Notify - create a device binding from a POST HTTP request
|
||||||
|
@app.route('/register', methods=['POST'])
|
||||||
|
def register():
|
||||||
|
# get credentials for environment variables
|
||||||
|
account_sid = os.environ['TWILIO_ACCOUNT_SID']
|
||||||
|
api_key = os.environ['TWILIO_API_KEY']
|
||||||
|
api_secret = os.environ['TWILIO_API_SECRET']
|
||||||
|
service_sid = os.environ['TWILIO_NOTIFICATION_SERVICE_SID']
|
||||||
|
|
||||||
|
# Initialize the Twilio client
|
||||||
|
client = Client(api_key, api_secret, account_sid)
|
||||||
|
|
||||||
|
# Body content
|
||||||
|
content = request.get_json()
|
||||||
|
|
||||||
|
# Get a reference to the notification service
|
||||||
|
service = client.notify.v1.services(service_sid)
|
||||||
|
|
||||||
|
# Create the binding
|
||||||
|
binding = service.bindings.create(
|
||||||
|
endpoint=content["endpoint"],
|
||||||
|
identity=content["identity"],
|
||||||
|
binding_type=content["BindingType"],
|
||||||
|
address=content["Address"])
|
||||||
|
|
||||||
|
print binding
|
||||||
|
|
||||||
|
# Return success message
|
||||||
|
return jsonify(message="Binding created!")
|
||||||
|
|
||||||
|
# Notify - send a notification from a POST HTTP request
|
||||||
|
@app.route('/send-notification', methods=['POST'])
|
||||||
|
def send_notification():
|
||||||
|
# get credentials for environment variables
|
||||||
|
account_sid = os.environ['TWILIO_ACCOUNT_SID']
|
||||||
|
api_key = os.environ['TWILIO_API_KEY']
|
||||||
|
api_secret = os.environ['TWILIO_API_SECRET']
|
||||||
|
service_sid = os.environ['TWILIO_NOTIFICATION_SERVICE_SID']
|
||||||
|
|
||||||
|
# Initialize the Twilio client
|
||||||
|
client = Client(api_key, api_secret, account_sid)
|
||||||
|
|
||||||
|
service = client.notify.v1.services(service_sid)
|
||||||
|
|
||||||
|
# Create a notification for a given identity
|
||||||
|
identity = request.form.get('identity')
|
||||||
|
notification = service.notifications.create(
|
||||||
|
identity=identity,
|
||||||
|
body='Hello ' + identity + '!'
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify(message="Notification created!")
|
||||||
|
|
||||||
|
@app.route('/<path:path>')
|
||||||
|
def static_file(path):
|
||||||
|
return app.send_static_file(path)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
||||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Flask==0.10.1
|
||||||
|
fake-factory==0.5.3
|
||||||
|
twilio==6.0.0rc12
|
||||||
|
python-dotenv==0.6.0
|
||||||
72
static/config-check.js
Normal file
72
static/config-check.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
$(function() {
|
||||||
|
$.get('/config', function(response) {
|
||||||
|
configureField(response, 'TWILIO_ACCOUNT_SID','twilioAccountSID',false);
|
||||||
|
configureField(response, 'TWILIO_API_KEY','twilioAPIKey',false);
|
||||||
|
configureField(response, 'TWILIO_API_SECRET','twilioAPISecret',true);
|
||||||
|
configureField(response, 'TWILIO_CONFIGURATION_SID','twilioConfigurationSID',false);
|
||||||
|
configureField(response, 'TWILIO_NOTIFICATION_SERVICE_SID','twilioNotificationServiceSID',false);
|
||||||
|
configureField(response, 'TWILIO_APN_CREDENTIAL_SID','twilioAPNCredentialSID',false);
|
||||||
|
configureField(response, 'TWILIO_GCM_CREDENTIAL_SID','twilioGCMCredentialSID',false);
|
||||||
|
configureField(response, 'TWILIO_IPM_SERVICE_SID','twilioIPMServiceSID',false);
|
||||||
|
configureField(response, 'TWILIO_SYNC_SERVICE_SID','twilioSyncServiceSID',false);
|
||||||
|
|
||||||
|
//configure individual product buttons
|
||||||
|
if (response.TWILIO_ACCOUNT_SID && response.TWILIO_ACCOUNT_SID != '' &&
|
||||||
|
response.TWILIO_API_KEY && response.TWILIO_API_KEY != '' && response.TWILIO_API_SECRET) {
|
||||||
|
|
||||||
|
if (response.TWILIO_CONFIGURATION_SID && response.TWILIO_CONFIGURATION_SID != '') {
|
||||||
|
$('#videoDemoButton').addClass('btn-success');
|
||||||
|
} else {
|
||||||
|
$('#videoDemoButton').addClass('btn-danger');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.TWILIO_IPM_SERVICE_SID && response.TWILIO_IPM_SERVICE_SID != '') {
|
||||||
|
$('#ipmDemoButton').addClass('btn-success');
|
||||||
|
} else {
|
||||||
|
$('#ipmDemoButton').addClass('btn-danger');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.TWILIO_SYNC_SERVICE_SID && response.TWILIO_SYNC_SERVICE_SID != '') {
|
||||||
|
$('#syncDemoButton').addClass('btn-success');
|
||||||
|
} else {
|
||||||
|
$('#syncDemoButton').addClass('btn-danger');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.TWILIO_NOTIFICATION_SERVICE_SID && response.TWILIO_NOTIFICATION_SERVICE_SID != '') {
|
||||||
|
$('#notifyDemoButton').addClass('btn-success');
|
||||||
|
} else {
|
||||||
|
$('#notifyDemoButton').addClass('btn-danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#videoDemoButton').addClass('btn-danger');
|
||||||
|
$('#ipmDemoButton').addClass('btn-danger');
|
||||||
|
$('#syncDemoButton').addClass('btn-danger');
|
||||||
|
$('#notifyDemoButton').addClass('btn-danger');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var configureField = function(response, keyName,elementId,masked) {
|
||||||
|
if (masked) {
|
||||||
|
if (response[keyName]) {
|
||||||
|
$('#' + elementId).html('Configured properly');
|
||||||
|
$('#' + elementId).addClass('set');
|
||||||
|
} else {
|
||||||
|
$('#' + elementId).html('Not configured in .env');
|
||||||
|
$('#' + elementId).addClass('unset');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (response[keyName] && response[keyName] != '') {
|
||||||
|
$('#' + elementId).html(response[keyName]);
|
||||||
|
$('#' + elementId).addClass('set');
|
||||||
|
} else {
|
||||||
|
$('#' + elementId).html('Not configured in .env');
|
||||||
|
$('#' + elementId).addClass('unset');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
});
|
||||||
7
static/index.css
Normal file
7
static/index.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.config-value.set {
|
||||||
|
color:seagreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-value.unset {
|
||||||
|
color:darkred;
|
||||||
|
}
|
||||||
74
static/index.html
Normal file
74
static/index.html
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Twilio Server Starter Kit</title>
|
||||||
|
|
||||||
|
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="index.css">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Twilio Server Starter Kit Environment Setup</h1>
|
||||||
|
<h2>Account Information</h2>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<tr>
|
||||||
|
<td class="config-key">TWILIO_ACCOUNT_SID</td>
|
||||||
|
<td class="config-value" id="twilioAccountSID"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="config-key">TWILIO_API_KEY</td>
|
||||||
|
<td class="config-value" id="twilioAPIKey"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="config-key">TWILIO_API_SECRET</td>
|
||||||
|
<td class="config-value" id="twilioAPISecret"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<h2>Products</h2>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<tr>
|
||||||
|
<td class="config-product">Video</td>
|
||||||
|
<td class="config-key">TWILIO_CONFIGURATION_SID</td>
|
||||||
|
<td class="config-value" id="twilioConfigurationSID"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="config-product">Notify</td>
|
||||||
|
<td class="config-key">TWILIO_NOTIFICATION_SERVICE_SID</td>
|
||||||
|
<td class="config-value" id="twilioNotificationServiceSID"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="config-product">Notify</td>
|
||||||
|
<td class="config-key">TWILIO_APN_CREDENTIAL_SID</td>
|
||||||
|
<td class="config-value" id="twilioAPNCredentialSID"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="config-product">Notify</td>
|
||||||
|
<td class="config-key">TWILIO_GCM_CREDENTIAL_SID</td>
|
||||||
|
<td class="config-value" id="twilioGCMCredentialSID"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="config-product">IP Messaging</td>
|
||||||
|
<td class="config-key">TWILIO_IPM_SERVICE_SID</td>
|
||||||
|
<td class="config-value" id="twilioIPMServiceSID"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="config-product">Sync</td>
|
||||||
|
<td class="config-key">TWILIO_SYNC_SERVICE_SID</td>
|
||||||
|
<td class="config-value" id="twilioSyncServiceSID"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h1>Demos</h1>
|
||||||
|
<a id="videoDemoButton" class="btn btn-lg" href="/video/">Video</a>
|
||||||
|
<a id="syncDemoButton" class="btn btn-lg" href="/sync/">Sync</a>
|
||||||
|
<a id="notifyDemoButton" class="btn btn-lg" href="/notify/">Notify</a>
|
||||||
|
<a id="ipmDemoButton" class="btn btn-lg" href="/ipmessaging/">IP Messaging</a>
|
||||||
|
</div> <!-- container -->
|
||||||
|
|
||||||
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||||
|
<script src="/config-check.js"></script>
|
||||||
|
</body>
|
||||||
|
</script>
|
||||||
90
static/ipmessaging/index.css
Executable file
90
static/ipmessaging/index.css
Executable file
@@ -0,0 +1,90 @@
|
|||||||
|
* {
|
||||||
|
box-sizing:border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
padding:0;
|
||||||
|
margin:0;
|
||||||
|
height:100%;
|
||||||
|
width:100%;
|
||||||
|
color:#dedede;
|
||||||
|
background-color: #849091;
|
||||||
|
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
width:100%;
|
||||||
|
position:absolute;
|
||||||
|
text-align:center;
|
||||||
|
bottom:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a, header a:visited {
|
||||||
|
font-size:18px;
|
||||||
|
color:#dedede;
|
||||||
|
text-decoration:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a:hover {
|
||||||
|
text-decoration:underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
height:70%;
|
||||||
|
background-color:#2B2B2A;
|
||||||
|
}
|
||||||
|
|
||||||
|
section input {
|
||||||
|
display:block;
|
||||||
|
height:52px;
|
||||||
|
width:800px;
|
||||||
|
margin:10px auto;
|
||||||
|
outline:none;
|
||||||
|
background-color:transparent;
|
||||||
|
border:none;
|
||||||
|
border-bottom:1px solid #2B2B2A;
|
||||||
|
padding:0;
|
||||||
|
font-size:42px;
|
||||||
|
color:#eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages {
|
||||||
|
background-color:#232323;
|
||||||
|
padding:10px;
|
||||||
|
height:100%;
|
||||||
|
width:800px;
|
||||||
|
margin:0 auto;
|
||||||
|
overflow-y:auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages p {
|
||||||
|
margin:5px 0;
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
margin:5px 0;
|
||||||
|
font-style:italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-container {
|
||||||
|
margin:5px 0;
|
||||||
|
color:#fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-container .username {
|
||||||
|
display:inline-block;
|
||||||
|
margin-right:5px;
|
||||||
|
font-weight:bold;
|
||||||
|
color:#849091;
|
||||||
|
}
|
||||||
|
|
||||||
|
.me, .username.me {
|
||||||
|
font-weight:bold;
|
||||||
|
color:cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-container .username.me {
|
||||||
|
display:inline-block;
|
||||||
|
margin-right:5px;
|
||||||
|
}
|
||||||
27
static/ipmessaging/index.html
Executable file
27
static/ipmessaging/index.html
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Twilio IP Messaging Quickstart</title>
|
||||||
|
<link rel="shortcut icon" href="//www.twilio.com/marketing/bundles/marketing/img/favicons/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
|
||||||
|
<link rel="stylesheet" href="index.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="https://www.twilio.com/docs/api/ip-messaging/guides/quickstart-js"
|
||||||
|
target="_blank">Read the getting started guide
|
||||||
|
<i class="fa fa-fw fa-external-link"></i>
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div id="messages"></div>
|
||||||
|
<input id="chat-input" type="text" placeholder="say anything" autofocus/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="//media.twiliocdn.com/sdk/js/common/v0.1/twilio-common.min.js"></script>
|
||||||
|
<script src="//media.twiliocdn.com/sdk/js/ip-messaging/v0.10/twilio-ip-messaging.min.js"></script>
|
||||||
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||||
|
<script src="index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
108
static/ipmessaging/index.js
Executable file
108
static/ipmessaging/index.js
Executable file
@@ -0,0 +1,108 @@
|
|||||||
|
$(function() {
|
||||||
|
// Get handle to the chat div
|
||||||
|
var $chatWindow = $('#messages');
|
||||||
|
|
||||||
|
// Manages the state of our access token we got from the server
|
||||||
|
var accessManager;
|
||||||
|
|
||||||
|
// Our interface to the IP Messaging service
|
||||||
|
var messagingClient;
|
||||||
|
|
||||||
|
// A handle to the "general" chat channel - the one and only channel we
|
||||||
|
// will have in this sample app
|
||||||
|
var generalChannel;
|
||||||
|
|
||||||
|
// The server will assign the client a random username - store that value
|
||||||
|
// here
|
||||||
|
var username;
|
||||||
|
|
||||||
|
// Helper function to print info messages to the chat window
|
||||||
|
function print(infoMessage, asHtml) {
|
||||||
|
var $msg = $('<div class="info">');
|
||||||
|
if (asHtml) {
|
||||||
|
$msg.html(infoMessage);
|
||||||
|
} else {
|
||||||
|
$msg.text(infoMessage);
|
||||||
|
}
|
||||||
|
$chatWindow.append($msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to print chat message to the chat window
|
||||||
|
function printMessage(fromUser, message) {
|
||||||
|
var $user = $('<span class="username">').text(fromUser + ':');
|
||||||
|
if (fromUser === username) {
|
||||||
|
$user.addClass('me');
|
||||||
|
}
|
||||||
|
var $message = $('<span class="message">').text(message);
|
||||||
|
var $container = $('<div class="message-container">');
|
||||||
|
$container.append($user).append($message);
|
||||||
|
$chatWindow.append($container);
|
||||||
|
$chatWindow.scrollTop($chatWindow[0].scrollHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert the user they have been assigned a random username
|
||||||
|
print('Logging in...');
|
||||||
|
|
||||||
|
// Get an access token for the current user, passing a username (identity)
|
||||||
|
// and a device ID - for browser-based apps, we'll always just use the
|
||||||
|
// value "browser"
|
||||||
|
$.getJSON('/token', {
|
||||||
|
device: 'browser'
|
||||||
|
}, function(data) {
|
||||||
|
// Alert the user they have been assigned a random username
|
||||||
|
username = data.identity;
|
||||||
|
print('You have been assigned a random username of: '
|
||||||
|
+ '<span class="me">' + username + '</span>', true);
|
||||||
|
|
||||||
|
// Initialize the IP messaging client
|
||||||
|
accessManager = new Twilio.AccessManager(data.token);
|
||||||
|
messagingClient = new Twilio.IPMessaging.Client(accessManager);
|
||||||
|
|
||||||
|
// Get the general chat channel, which is where all the messages are
|
||||||
|
// sent in this simple application
|
||||||
|
print('Attempting to join "general" chat channel...');
|
||||||
|
var promise = messagingClient.getChannelByUniqueName('general');
|
||||||
|
promise.then(function(channel) {
|
||||||
|
generalChannel = channel;
|
||||||
|
if (!generalChannel) {
|
||||||
|
// If it doesn't exist, let's create it
|
||||||
|
messagingClient.createChannel({
|
||||||
|
uniqueName: 'general',
|
||||||
|
friendlyName: 'General Chat Channel'
|
||||||
|
}).then(function(channel) {
|
||||||
|
console.log('Created general channel:');
|
||||||
|
console.log(channel);
|
||||||
|
generalChannel = channel;
|
||||||
|
setupChannel();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('Found general channel:');
|
||||||
|
console.log(generalChannel);
|
||||||
|
setupChannel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up channel after it has been found
|
||||||
|
function setupChannel() {
|
||||||
|
// Join the general channel
|
||||||
|
generalChannel.join().then(function(channel) {
|
||||||
|
print('Joined channel as '
|
||||||
|
+ '<span class="me">' + username + '</span>.', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for new messages sent to the channel
|
||||||
|
generalChannel.on('messageAdded', function(message) {
|
||||||
|
printMessage(message.author, message.body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a new message to the general channel
|
||||||
|
var $input = $('#chat-input');
|
||||||
|
$input.on('keydown', function(e) {
|
||||||
|
if (e.keyCode == 13) {
|
||||||
|
generalChannel.sendMessage($input.val())
|
||||||
|
$input.val('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
35
static/notify/index.html
Normal file
35
static/notify/index.html
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Hello App! - Notify Quickstart</title>
|
||||||
|
<link rel="shortcut icon" href="//www.twilio.com/marketing/bundles/marketing/img/favicons/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
|
||||||
|
<link rel="stylesheet" href="notify.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="https://www.twilio.com/docs/api/notifications"
|
||||||
|
target="_blank">
|
||||||
|
Read the Twilio Notify guide
|
||||||
|
<i class="fa fa-fw fa-external-link"></i>
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h1>Send Notification</h1>
|
||||||
|
<input type="text" id="identityInput" size="30"/>
|
||||||
|
<p/>
|
||||||
|
<input type="submit" id="sendNotificationButton" value="Send Notification"/>
|
||||||
|
|
||||||
|
<div id="message">
|
||||||
|
Welcome to Notify!
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>After you set up a notification binding, go ahead and send a notification to that identity!</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||||
|
<script src="notify.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
40
static/notify/notify.css
Normal file
40
static/notify/notify.css
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
* {
|
||||||
|
box-sizing:border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
padding:0;
|
||||||
|
margin:0;
|
||||||
|
height:100%;
|
||||||
|
width:100%;
|
||||||
|
color:#dedede;
|
||||||
|
background-color: #849091;
|
||||||
|
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
width:100%;
|
||||||
|
position:absolute;
|
||||||
|
text-align:center;
|
||||||
|
bottom:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a, header a:visited {
|
||||||
|
font-size:18px;
|
||||||
|
color:#dedede;
|
||||||
|
text-decoration:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a:hover {
|
||||||
|
text-decoration:underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
background-color:#2B2B2A;
|
||||||
|
text-align:center;
|
||||||
|
padding:16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#message {
|
||||||
|
padding:6px;
|
||||||
|
}
|
||||||
12
static/notify/notify.js
Normal file
12
static/notify/notify.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
$(function() {
|
||||||
|
|
||||||
|
$('#sendNotificationButton').on('click', function() {
|
||||||
|
$.post('/send-notification', {
|
||||||
|
identity: $('#identityInput').val()
|
||||||
|
}, function(response) {
|
||||||
|
$('#identityInput').val('');
|
||||||
|
$('#message').html(response.message);
|
||||||
|
console.log(response);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
63
static/sync/index.css
Executable file
63
static/sync/index.css
Executable file
@@ -0,0 +1,63 @@
|
|||||||
|
* {
|
||||||
|
box-sizing:border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
padding:0;
|
||||||
|
margin:0;
|
||||||
|
height:100%;
|
||||||
|
width:100%;
|
||||||
|
color:#dedede;
|
||||||
|
background-color: #849091;
|
||||||
|
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
width:100%;
|
||||||
|
position:absolute;
|
||||||
|
text-align:center;
|
||||||
|
bottom:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a, header a:visited {
|
||||||
|
font-size:18px;
|
||||||
|
color:#dedede;
|
||||||
|
text-decoration:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a:hover {
|
||||||
|
text-decoration:underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
background-color:#2B2B2A;
|
||||||
|
text-align:center;
|
||||||
|
padding:16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
cursor:pointer;
|
||||||
|
background-color:#000;
|
||||||
|
color:#fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#message {
|
||||||
|
padding:6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#board {
|
||||||
|
width: 33%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#board .board-row {
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#board .board-row button {
|
||||||
|
width: 30%;
|
||||||
|
height: 100px;
|
||||||
|
font-size: 50px;
|
||||||
|
}
|
||||||
51
static/sync/index.html
Executable file
51
static/sync/index.html
Executable file
@@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Tic-Tac-Twilio - Sync Quickstart</title>
|
||||||
|
<link rel="shortcut icon" href="//www.twilio.com/marketing/bundles/marketing/img/favicons/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
|
||||||
|
<link rel="stylesheet" href="index.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="https://www.twilio.com/docs/api/sync/quickstart-js"
|
||||||
|
target="_blank">
|
||||||
|
Read the getting started guide
|
||||||
|
<i class="fa fa-fw fa-external-link"></i>
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h1>Tic-Tac-Twilio</h1>
|
||||||
|
|
||||||
|
<div id="board">
|
||||||
|
<div class="board-row">
|
||||||
|
<button type="button" data-row="0" data-col="0" disabled> </button>
|
||||||
|
<button type="button" data-row="0" data-col="1" disabled> </button>
|
||||||
|
<button type="button" data-row="0" data-col="2" disabled> </button>
|
||||||
|
</div>
|
||||||
|
<div class="board-row">
|
||||||
|
<button type="button" data-row="1" data-col="0" disabled> </button>
|
||||||
|
<button type="button" data-row="1" data-col="1" disabled> </button>
|
||||||
|
<button type="button" data-row="1" data-col="2" disabled> </button>
|
||||||
|
</div>
|
||||||
|
<div class="board-row">
|
||||||
|
<button type="button" data-row="2" data-col="0" disabled> </button>
|
||||||
|
<button type="button" data-row="2" data-col="1" disabled> </button>
|
||||||
|
<button type="button" data-row="2" data-col="2" disabled> </button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="message">
|
||||||
|
Welcome! Initializing Sync...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Open this page in a few tabs to test!</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="//media.twiliocdn.com/sdk/js/sync/v0.3/twilio-sync.min.js"></script>
|
||||||
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||||
|
<script src="index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
123
static/sync/index.js
Executable file
123
static/sync/index.js
Executable file
@@ -0,0 +1,123 @@
|
|||||||
|
$(function () {
|
||||||
|
//We'll use message to tell the user what's happening
|
||||||
|
var $message = $('#message');
|
||||||
|
|
||||||
|
//Get handle to the game board buttons
|
||||||
|
var $buttons = $('#board .board-row button');
|
||||||
|
|
||||||
|
//Manages the state of our access token we got from the server
|
||||||
|
var accessManager;
|
||||||
|
|
||||||
|
//Our interface to the Sync service
|
||||||
|
var syncClient;
|
||||||
|
|
||||||
|
//We're going to use a single Sync document, our simplest
|
||||||
|
//synchronisation primitive, for this demo
|
||||||
|
var syncDoc;
|
||||||
|
|
||||||
|
//Get an access token for the current user, passing a device ID
|
||||||
|
//In browser-based apps, every tab is like its own unique device
|
||||||
|
//synchronizing state -- so we'll use a random UUID to identify
|
||||||
|
//this tab.
|
||||||
|
$.getJSON('/token', {
|
||||||
|
device: getDeviceId()
|
||||||
|
}, function (tokenResponse) {
|
||||||
|
//Initialize the Sync client
|
||||||
|
syncClient = new Twilio.Sync.Client(tokenResponse.token);
|
||||||
|
|
||||||
|
//Let's pop a message on the screen to show that Sync is ready
|
||||||
|
$message.html('Sync initialized!');
|
||||||
|
|
||||||
|
//Now that Sync is active, lets enable our game board
|
||||||
|
$buttons.attr('disabled', false);
|
||||||
|
|
||||||
|
//This code will create and/or open a Sync document
|
||||||
|
//Note the use of promises
|
||||||
|
syncClient.document('SyncGame').then(function(doc) {
|
||||||
|
//Lets store it in our global variable
|
||||||
|
syncDoc = doc;
|
||||||
|
|
||||||
|
//Initialize game board UI to current state (if it exists)
|
||||||
|
var data = syncDoc.get();
|
||||||
|
if (data.board) {
|
||||||
|
updateUserInterface(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Let's subscribe to changes on this document, so when something
|
||||||
|
//changes on this document, we can trigger our UI to update
|
||||||
|
syncDoc.on('updated', updateUserInterface);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
//Whenever a board button is clicked:
|
||||||
|
$buttons.on('click', function (e) {
|
||||||
|
//Toggle the value: X, O, or empty
|
||||||
|
toggleCellValue($(e.target));
|
||||||
|
|
||||||
|
//Update the document
|
||||||
|
var data = readGameBoardFromUserInterface();
|
||||||
|
|
||||||
|
//Send updated document to Sync
|
||||||
|
//This should trigger "updated" events on other clients
|
||||||
|
syncDoc.set(data);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
//Toggle the value: X, O, or empty ( for UI)
|
||||||
|
function toggleCellValue($cell) {
|
||||||
|
var cellValue = $cell.html();
|
||||||
|
|
||||||
|
if (cellValue === 'X') {
|
||||||
|
$cell.html('O');
|
||||||
|
} else if (cellValue === 'O') {
|
||||||
|
$cell.html(' ');
|
||||||
|
} else {
|
||||||
|
$cell.html('X');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate random UUID to identify this browser tab
|
||||||
|
//For a more robust solution consider a library like
|
||||||
|
//fingerprintjs2: https://github.com/Valve/fingerprintjs2
|
||||||
|
function getDeviceId() {
|
||||||
|
return 'browser-' +
|
||||||
|
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||||
|
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Read the state of the UI and create a new document
|
||||||
|
function readGameBoardFromUserInterface() {
|
||||||
|
var board = [
|
||||||
|
['', '', ''],
|
||||||
|
['', '', ''],
|
||||||
|
['', '', '']
|
||||||
|
];
|
||||||
|
|
||||||
|
for (var row = 0; row < 3; row++) {
|
||||||
|
for (var col = 0; col < 3; col++) {
|
||||||
|
var selector = '[data-row="' + row + '"]' +
|
||||||
|
'[data-col="' + col + '"]';
|
||||||
|
board[row][col] = $(selector).html().replace(' ', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {board: board};
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update the buttons on the board to match our document
|
||||||
|
function updateUserInterface(data) {
|
||||||
|
for (var row = 0; row < 3; row++) {
|
||||||
|
for (var col = 0; col < 3; col++) {
|
||||||
|
var selector = '[data-row="' + row + '"]' +
|
||||||
|
'[data-col="' + col + '"]';
|
||||||
|
var cellValue = data.board[row][col];
|
||||||
|
$(selector).html(cellValue === '' ? ' ' : cellValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
29
static/video/index.html
Executable file
29
static/video/index.html
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Twilio Video - Video Quickstart</title>
|
||||||
|
<link rel="stylesheet" href="site.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="remote-media"></div>
|
||||||
|
<div id="controls">
|
||||||
|
<div id="preview">
|
||||||
|
<p class="instructions">Hello Beautiful</p>
|
||||||
|
<div id="local-media"></div>
|
||||||
|
<button id="button-preview">Preview My Camera</button>
|
||||||
|
</div>
|
||||||
|
<div id="room-controls">
|
||||||
|
<p class="instructions">Room Name:</p>
|
||||||
|
<input id="room-name" type="text" placeholder="Enter a room name" />
|
||||||
|
<button id="button-join">Join Room</button>
|
||||||
|
<button id="button-leave">Leave Room</button>
|
||||||
|
</div>
|
||||||
|
<div id="log"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="//media.twiliocdn.com/sdk/js/common/v0.1/twilio-common.min.js"></script>
|
||||||
|
<script src="//media.twiliocdn.com/sdk/js/video/releases/1.0.0-beta2/twilio-video.js"></script>
|
||||||
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||||
|
<script src="quickstart.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
120
static/video/quickstart.js
Executable file
120
static/video/quickstart.js
Executable file
@@ -0,0 +1,120 @@
|
|||||||
|
var videoClient;
|
||||||
|
var activeRoom;
|
||||||
|
var previewMedia;
|
||||||
|
var identity;
|
||||||
|
var roomName;
|
||||||
|
|
||||||
|
// Check for WebRTC
|
||||||
|
if (!navigator.webkitGetUserMedia && !navigator.mozGetUserMedia) {
|
||||||
|
alert('WebRTC is not available in your browser.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we are about to transition away from this page, disconnect
|
||||||
|
// from the room, if joined.
|
||||||
|
window.addEventListener('beforeunload', leaveRoomIfJoined);
|
||||||
|
|
||||||
|
$.getJSON('/token', function (data) {
|
||||||
|
identity = data.identity;
|
||||||
|
|
||||||
|
// Create a Conversations Client and connect to Twilio
|
||||||
|
videoClient = new Twilio.Video.Client(data.token);
|
||||||
|
document.getElementById('room-controls').style.display = 'block';
|
||||||
|
|
||||||
|
// Bind button to join room
|
||||||
|
document.getElementById('button-join').onclick = function () {
|
||||||
|
roomName = document.getElementById('room-name').value;
|
||||||
|
if (roomName) {
|
||||||
|
log("Joining room '" + roomName + "'...");
|
||||||
|
|
||||||
|
videoClient.connect({ to: roomName}).then(roomJoined,
|
||||||
|
function(error) {
|
||||||
|
log('Could not connect to Twilio: ' + error.message);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert('Please enter a room name.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bind button to leave room
|
||||||
|
document.getElementById('button-leave').onclick = function () {
|
||||||
|
log('Leaving room...');
|
||||||
|
activeRoom.disconnect();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Successfully connected!
|
||||||
|
function roomJoined(room) {
|
||||||
|
activeRoom = room;
|
||||||
|
|
||||||
|
log("Joined as '" + identity + "'");
|
||||||
|
document.getElementById('button-join').style.display = 'none';
|
||||||
|
document.getElementById('button-leave').style.display = 'inline';
|
||||||
|
|
||||||
|
// Draw local video, if not already previewing
|
||||||
|
if (!previewMedia) {
|
||||||
|
room.localParticipant.media.attach('#local-media');
|
||||||
|
}
|
||||||
|
|
||||||
|
room.participants.forEach(function(participant) {
|
||||||
|
log("Already in Room: '" + participant.identity + "'");
|
||||||
|
participant.media.attach('#remote-media');
|
||||||
|
});
|
||||||
|
|
||||||
|
// When a participant joins, draw their video on screen
|
||||||
|
room.on('participantConnected', function (participant) {
|
||||||
|
log("Joining: '" + participant.identity + "'");
|
||||||
|
participant.media.attach('#remote-media');
|
||||||
|
|
||||||
|
participant.on('disconnected', function (participant) {
|
||||||
|
log("Participant '" + participant.identity + "' left the room");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When a participant disconnects, note in log
|
||||||
|
room.on('participantDisconnected', function (participant) {
|
||||||
|
log("Participant '" + participant.identity + "' left the room");
|
||||||
|
participant.media.detach();
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the conversation ends, stop capturing local video
|
||||||
|
// Also remove media for all remote participants
|
||||||
|
room.on('disconnected', function () {
|
||||||
|
log('Left');
|
||||||
|
room.localParticipant.media.detach();
|
||||||
|
room.participants.forEach(function(participant) {
|
||||||
|
participant.media.detach();
|
||||||
|
});
|
||||||
|
activeRoom = null;
|
||||||
|
document.getElementById('button-join').style.display = 'inline';
|
||||||
|
document.getElementById('button-leave').style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local video preview
|
||||||
|
document.getElementById('button-preview').onclick = function () {
|
||||||
|
if (!previewMedia) {
|
||||||
|
previewMedia = new Twilio.Video.LocalMedia();
|
||||||
|
Twilio.Video.getUserMedia().then(
|
||||||
|
function (mediaStream) {
|
||||||
|
previewMedia.addStream(mediaStream);
|
||||||
|
previewMedia.attach('#local-media');
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.error('Unable to access local media', error);
|
||||||
|
log('Unable to access Camera and Microphone');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Activity log
|
||||||
|
function log(message) {
|
||||||
|
var logDiv = document.getElementById('log');
|
||||||
|
logDiv.innerHTML += '<p>> ' + message + '</p>';
|
||||||
|
logDiv.scrollTop = logDiv.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function leaveRoomIfJoined() {
|
||||||
|
if (activeRoom) {
|
||||||
|
activeRoom.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
144
static/video/site.css
Executable file
144
static/video/site.css
Executable file
@@ -0,0 +1,144 @@
|
|||||||
|
@import url(https://fonts.googleapis.com/css?family=Share+Tech+Mono);
|
||||||
|
|
||||||
|
body,
|
||||||
|
p {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #272726;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#remote-media {
|
||||||
|
height: 43%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#remote-media video {
|
||||||
|
border: 1px solid #272726;
|
||||||
|
margin: 3em 2em;
|
||||||
|
height: 70%;
|
||||||
|
max-width: 27% !important;
|
||||||
|
background-color: #272726;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls {
|
||||||
|
padding: 3em;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls div {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls div#room-controls,
|
||||||
|
div#controls div#preview {
|
||||||
|
width: 16em;
|
||||||
|
margin: 0 1.5em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls p.instructions {
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
font-family: Helvetica-LightOblique, Helvetica, sans-serif;
|
||||||
|
font-style: oblique;
|
||||||
|
font-size: 1.25em;
|
||||||
|
color: #777776;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls button {
|
||||||
|
width: 15em;
|
||||||
|
height: 2.5em;
|
||||||
|
margin-top: 1.75em;
|
||||||
|
border-radius: 1em;
|
||||||
|
font-family: "Helvetica Light", Helvetica, sans-serif;
|
||||||
|
font-size: .8em;
|
||||||
|
font-weight: lighter;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls div#room-controls input {
|
||||||
|
font-family: Helvetica-LightOblique, Helvetica, sans-serif;
|
||||||
|
font-style: oblique;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls button:active {
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls div#preview div#local-media {
|
||||||
|
width: 270px;
|
||||||
|
height: 202px;
|
||||||
|
border: 1px solid #cececc;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjgwcHgiIGhlaWdodD0iODBweCIgdmlld0JveD0iMCAwIDgwIDgwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIj4KICAgIDwhLS0gR2VuZXJhdG9yOiBTa2V0Y2ggMy4zLjEgKDEyMDAyKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5GaWxsIDUxICsgRmlsbCA1MjwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxkZWZzPjwvZGVmcz4KICAgIDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPgogICAgICAgIDxnIGlkPSJjdW1tYWNrIiBza2V0Y2g6dHlwZT0iTVNMYXllckdyb3VwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTU5LjAwMDAwMCwgLTE3NDYuMDAwMDAwKSIgZmlsbD0iI0ZGRkZGRiI+CiAgICAgICAgICAgIDxnIGlkPSJGaWxsLTUxLSstRmlsbC01MiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTU5LjAwMDAwMCwgMTc0Ni4wMDAwMDApIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0zOS42ODYsMC43MyBDMTcuODUsMC43MyAwLjA4NSwxOC41IDAuMDg1LDQwLjMzIEMwLjA4NSw2Mi4xNyAxNy44NSw3OS45MyAzOS42ODYsNzkuOTMgQzYxLjUyMiw3OS45MyA3OS4yODcsNjIuMTcgNzkuMjg3LDQwLjMzIEM3OS4yODcsMTguNSA2MS41MjIsMC43MyAzOS42ODYsMC43MyBMMzkuNjg2LDAuNzMgWiBNMzkuNjg2LDEuNzMgQzYxLjAwNSwxLjczIDc4LjI4NywxOS4wMiA3OC4yODcsNDAuMzMgQzc4LjI4Nyw2MS42NSA2MS4wMDUsNzguOTMgMzkuNjg2LDc4LjkzIEMxOC4zNjcsNzguOTMgMS4wODUsNjEuNjUgMS4wODUsNDAuMzMgQzEuMDg1LDE5LjAyIDE4LjM2NywxLjczIDM5LjY4NiwxLjczIEwzOS42ODYsMS43MyBaIiBpZD0iRmlsbC01MSI+PC9wYXRoPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTTQ3Ljk2LDUzLjMzNSBMNDcuOTYsNTIuODM1IEwyMC4wOTMsNTIuODM1IEwyMC4wOTMsMjcuODI1IEw0Ny40NiwyNy44MjUgTDQ3LjQ2LDM4LjI1NSBMNTkuMjc5LDMwLjgwNSBMNTkuMjc5LDQ5Ljg1NSBMNDcuNDYsNDIuNDA1IEw0Ny40Niw1My4zMzUgTDQ3Ljk2LDUzLjMzNSBMNDcuOTYsNTIuODM1IEw0Ny45Niw1My4zMzUgTDQ4LjQ2LDUzLjMzNSBMNDguNDYsNDQuMjE1IEw2MC4yNzksNTEuNjY1IEw2MC4yNzksMjguOTk1IEw0OC40NiwzNi40NDUgTDQ4LjQ2LDI2LjgyNSBMMTkuMDkzLDI2LjgyNSBMMTkuMDkzLDUzLjgzNSBMNDguNDYsNTMuODM1IEw0OC40Niw1My4zMzUgTDQ3Ljk2LDUzLjMzNSIgaWQ9IkZpbGwtNTIiPjwvcGF0aD4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+);
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls div#preview div#local-media video {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls div#preview button#button-preview {
|
||||||
|
background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE3cHgiIGhlaWdodD0iMTJweCIgdmlld0JveD0iMCAwIDE3IDEyIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIj4KICAgIDwhLS0gR2VuZXJhdG9yOiBTa2V0Y2ggMy4zLjEgKDEyMDAyKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5GaWxsIDM0PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9ImN1bW1hY2siIHNrZXRjaDp0eXBlPSJNU0xheWVyR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjUuMDAwMDAwLCAtMTkwOS4wMDAwMDApIiBmaWxsPSIjMEEwQjA5Ij4KICAgICAgICAgICAgPHBhdGggZD0iTTEzNi40NzEsMTkxOS44NyBMMTM2LjQ3MSwxOTE5LjYyIEwxMjUuNzY3LDE5MTkuNjIgTDEyNS43NjcsMTkxMC4wOCBMMTM2LjIyMSwxOTEwLjA4IEwxMzYuMjIxLDE5MTQuMTUgTDE0MC43ODUsMTkxMS4yNyBMMTQwLjc4NSwxOTE4LjQyIEwxMzYuMjIxLDE5MTUuNTUgTDEzNi4yMjEsMTkxOS44NyBMMTM2LjQ3MSwxOTE5Ljg3IEwxMzYuNDcxLDE5MTkuNjIgTDEzNi40NzEsMTkxOS44NyBMMTM2LjcyMSwxOTE5Ljg3IEwxMzYuNzIxLDE5MTYuNDUgTDE0MS4yODUsMTkxOS4zMyBMMTQxLjI4NSwxOTEwLjM3IEwxMzYuNzIxLDE5MTMuMjQgTDEzNi43MjEsMTkwOS41OCBMMTI1LjI2NywxOTA5LjU4IEwxMjUuMjY3LDE5MjAuMTIgTDEzNi43MjEsMTkyMC4xMiBMMTM2LjcyMSwxOTE5Ljg3IEwxMzYuNDcxLDE5MTkuODciIGlkPSJGaWxsLTM0IiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N2Zz4=)1em center no-repeat #fff;
|
||||||
|
border: none;
|
||||||
|
padding-left: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls div#log {
|
||||||
|
border: 1px solid #686865;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls div#room-controls {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls div#room-controls input {
|
||||||
|
width: 100%;
|
||||||
|
height: 2.5em;
|
||||||
|
padding: .5em;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls div#room-controls button {
|
||||||
|
color: #fff;
|
||||||
|
background: 0 0;
|
||||||
|
border: 1px solid #686865;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls div#room-controls button#button-leave {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls div#log {
|
||||||
|
width: 35%;
|
||||||
|
height: 9.5em;
|
||||||
|
margin-top: 2.75em;
|
||||||
|
text-align: left;
|
||||||
|
padding: 1.5em;
|
||||||
|
float: right;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#controls div#log p {
|
||||||
|
color: #686865;
|
||||||
|
font-family: 'Share Tech Mono', 'Courier New', Courier, fixed-width;
|
||||||
|
font-size: 1.25em;
|
||||||
|
line-height: 1.25em;
|
||||||
|
margin-left: 1em;
|
||||||
|
text-indent: -1.25em;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user