├── .env.example ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── app.rb └── public ├── chat ├── index.css ├── index.html └── index.js ├── config-check.js ├── index.css ├── index.html ├── notify ├── facebook_messenger.html ├── index.html ├── notify.css └── notify.js └── sync ├── index.css ├── index.html └── index.js /.env.example: -------------------------------------------------------------------------------- 1 | # For Sinatra configuration, defaults to production. 2 | # For local development, set this value to development 3 | APP_ENV=production 4 | 5 | # Required for all uses 6 | TWILIO_ACCOUNT_SID= 7 | TWILIO_API_KEY= 8 | TWILIO_API_SECRET= 9 | 10 | # Required for Chat 11 | # TWILIO_CHAT_SERVICE_SID= 12 | 13 | # Required for Notify 14 | # TWILIO_NOTIFICATION_SERVICE_SID= 15 | 16 | # Optional for Sync 17 | # By default, this app will use the default instance for your account (named "default"). 18 | # TWILIO_SYNC_SERVICE_SID= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | 3 | *.gem 4 | *.rbc 5 | /.config 6 | /coverage/ 7 | /InstalledFiles 8 | /pkg/ 9 | /spec/reports/ 10 | /spec/examples.txt 11 | /test/tmp/ 12 | /test/version_tmp/ 13 | /tmp/ 14 | 15 | # Used by dotenv library to load environment variables. 16 | .env 17 | 18 | ## Specific to RubyMotion: 19 | .dat* 20 | .repl_history 21 | build/ 22 | *.bridgesupport 23 | build-iPhoneOS/ 24 | build-iPhoneSimulator/ 25 | 26 | ## Specific to RubyMotion (use of CocoaPods): 27 | # 28 | # We recommend against adding the Pods directory to your .gitignore. However 29 | # you should judge for yourself, the pros and cons are mentioned at: 30 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 31 | # 32 | # vendor/Pods/ 33 | 34 | ## Documentation cache and generated files: 35 | /.yardoc/ 36 | /_yardoc/ 37 | /doc/ 38 | /rdoc/ 39 | 40 | ## Environment normalization: 41 | /.bundle/ 42 | /vendor/bundle 43 | /lib/bundler/man/ 44 | 45 | # for a library or gem, you might want to ignore these files since the code is 46 | # intended to run in multiple environments; otherwise, check them in: 47 | # Gemfile.lock 48 | .ruby-version 49 | # .ruby-gemset 50 | 51 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 52 | .rvmrc 53 | 54 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'dotenv' 3 | gem 'faker' 4 | gem 'sinatra' 5 | gem 'sinatra-contrib' 6 | gem 'rack-contrib' 7 | gem 'facets' 8 | gem 'twilio-ruby' 9 | gem 'thin' 10 | gem 'nokogiri', '>= 1.12.5' 11 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | concurrent-ruby (1.1.9) 5 | daemons (1.4.1) 6 | dotenv (2.7.6) 7 | eventmachine (1.2.7) 8 | facets (3.1.0) 9 | faker (2.19.0) 10 | i18n (>= 1.6, < 2) 11 | faraday (1.8.0) 12 | faraday-em_http (~> 1.0) 13 | faraday-em_synchrony (~> 1.0) 14 | faraday-excon (~> 1.1) 15 | faraday-httpclient (~> 1.0.1) 16 | faraday-net_http (~> 1.0) 17 | faraday-net_http_persistent (~> 1.1) 18 | faraday-patron (~> 1.0) 19 | faraday-rack (~> 1.0) 20 | multipart-post (>= 1.2, < 3) 21 | ruby2_keywords (>= 0.0.4) 22 | faraday-em_http (1.0.0) 23 | faraday-em_synchrony (1.0.0) 24 | faraday-excon (1.1.0) 25 | faraday-httpclient (1.0.1) 26 | faraday-net_http (1.0.1) 27 | faraday-net_http_persistent (1.2.0) 28 | faraday-patron (1.0.0) 29 | faraday-rack (1.0.0) 30 | i18n (1.8.10) 31 | concurrent-ruby (~> 1.0) 32 | jwt (2.2.3) 33 | mini_portile2 (2.6.1) 34 | multi_json (1.15.0) 35 | multipart-post (2.1.1) 36 | mustermann (1.1.1) 37 | ruby2_keywords (~> 0.0.1) 38 | nokogiri (1.12.5) 39 | mini_portile2 (~> 2.6.1) 40 | racc (~> 1.4) 41 | racc (1.5.2) 42 | rack (2.2.3) 43 | rack-contrib (2.3.0) 44 | rack (~> 2.0) 45 | rack-protection (2.1.0) 46 | rack 47 | ruby2_keywords (0.0.5) 48 | sinatra (2.1.0) 49 | mustermann (~> 1.0) 50 | rack (~> 2.2) 51 | rack-protection (= 2.1.0) 52 | tilt (~> 2.0) 53 | sinatra-contrib (2.1.0) 54 | multi_json 55 | mustermann (~> 1.0) 56 | rack-protection (= 2.1.0) 57 | sinatra (= 2.1.0) 58 | tilt (~> 2.0) 59 | thin (1.8.1) 60 | daemons (~> 1.0, >= 1.0.9) 61 | eventmachine (~> 1.0, >= 1.0.4) 62 | rack (>= 1, < 3) 63 | tilt (2.0.10) 64 | twilio-ruby (5.58.3) 65 | faraday (>= 0.9, < 2.0) 66 | jwt (>= 1.5, <= 2.5) 67 | nokogiri (>= 1.6, < 2.0) 68 | 69 | PLATFORMS 70 | ruby 71 | 72 | DEPENDENCIES 73 | dotenv 74 | facets 75 | faker 76 | nokogiri (>= 1.12.5) 77 | rack-contrib 78 | sinatra 79 | sinatra-contrib 80 | thin 81 | twilio-ruby 82 | 83 | BUNDLED WITH 84 | 2.2.15 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Twilio, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Twilio 3 | 4 | 5 | # Twilio SDK Starter Application for Ruby 6 | 7 | This sample project demonstrates how to use Twilio APIs in a Ruby web 8 | application. Once the app is up and running, check out [the home page](http://localhost:4567) 9 | to see which demos you can run. You'll find examples for [Chat](https://www.twilio.com/chat), 10 | [Notify](https://www.twilio.com/notify), and [Sync](https://www.twilio.com/sync). 11 | 12 | Let's get started! 13 | 14 | ## Configure the sample application 15 | 16 | To run the application, you'll need to gather your Twilio account credentials and configure them 17 | in a file named `.env`. To create this file from an example template, do the following in your 18 | Terminal. 19 | 20 | ```bash 21 | cp .env.example .env 22 | ``` 23 | 24 | Open `.env` in your favorite text editor and configure the following values. 25 | 26 | ### Configure account information 27 | 28 | Every sample in the demo requires some basic credentials from your Twilio account. Configure these first. 29 | 30 | | Config Value | Description | 31 | | :------------- |:------------- | 32 | `TWILIO_ACCOUNT_SID` | Your primary Twilio account identifier - find this [in the console here](https://www.twilio.com/console). 33 | `TWILIO_API_KEY` | Used to authenticate - [generate one here](https://www.twilio.com/console/dev-tools/api-keys). 34 | `TWILIO_API_SECRET` | Used to authenticate - [just like the above, you'll get one here](https://www.twilio.com/console/dev-tools/api-keys). 35 | 36 | #### A Note on API Keys 37 | 38 | When you generate an API key pair at the URLs above, your API Secret will only be shown once - 39 | make sure to save this information in a secure location, or possibly your `~/.bash_profile`. 40 | 41 | ### Configure Development vs Production Settings 42 | 43 | By default, this application will run in production mode - stack traces will not be visible in the web browser. If you would like to run this application in development locally, change the `APP_ENV` variable in your `.env` file. 44 | 45 | `APP_ENV=development` 46 | 47 | For more about development vs production, visit [Sinatra's configuration page](http://sinatrarb.com/configuration.html). 48 | 49 | ### Configure product-specific settings 50 | 51 | Depending on which demos you'd like to run, you may need to configure a few more values in your `.env` file. 52 | 53 | #### Configuring Twilio Sync 54 | 55 | Twilio Sync works out of the box, using default settings per account. Once you have your API keys configured, run the application (see below) and [open a browser](http://localhost:4567/sync)! 56 | 57 | #### Configuring Twilio Chat 58 | 59 | In addition to the above, you'll need to [generate a Chat Service](https://www.twilio.com/console/chat/services) in the Twilio Console. Put the result in your `.env` file. 60 | 61 | | Config Value | Where to get one. | 62 | | :------------- |:------------- | 63 | `TWILIO_CHAT_SERVICE_SID` | [Generate one in the Twilio Chat console](https://www.twilio.com/console/chat/services) 64 | 65 | With this in place, run the application (see below) and [open a browser](http://localhost:4567/chat)! 66 | 67 | #### Configuring Twilio Notify 68 | 69 | You will need to create a Notify Service and add at least one credential on the [Mobile Push Credential screen](https://www.twilio.com/console/notify/credentials) (such as Apple Push Notification Service or Firebase Cloud Messaging for Android) to send notifications using Notify. 70 | 71 | | Config Value | Where to get one. | 72 | | :------------- |:------------- | 73 | `TWILIO_NOTIFICATION_SERVICE_SID` | Generate one in the [Notify Console](https://www.twilio.com/console/notify/services) and put this in your `.env` file. 74 | A Push Credential | Generate one with Apple or Google and [configure it as a Notify credential](https://www.twilio.com/console/notify/credentials). 75 | 76 | Once you've done that, run the application (see below) and [open a browser](http://localhost:4567/notify)! 77 | 78 | ## Run the sample application 79 | 80 | Now that the application is configured, we need to install our dependencies with Bundler. 81 | 82 | ```bash 83 | bundle install 84 | ``` 85 | 86 | Now we should be all set! Run the application using the `bundle exec` command. 87 | 88 | ```bash 89 | bundle exec ruby app.rb 90 | ``` 91 | 92 | Your application should now be running at [http://localhost:4567/](http://localhost:4567/). 93 | 94 | ![Home Screen](https://cloud.githubusercontent.com/assets/809856/26252870/0bfd80ac-3c77-11e7-9252-2b19dff5d784.png) 95 | 96 | Check your config values, and follow the links to the demo applications! 97 | 98 | ## Running the SDK Starter Kit with ngrok 99 | 100 | If you are going to connect to this SDK Starter Kit with a mobile app (and you should try it out!), your phone won't be able to access localhost directly. You'll need to create a publicly accessible URL using a tool like [ngrok](https://ngrok.com/) to send HTTP/HTTPS traffic to a server running on your localhost. Use HTTPS to make web connections that retrieve a Twilio access token. 101 | 102 | ```bash 103 | ngrok http 4567 104 | ``` 105 | 106 | ## License 107 | MIT 108 | -------------------------------------------------------------------------------- /app.rb: -------------------------------------------------------------------------------- 1 | require 'twilio-ruby' 2 | require 'sinatra' 3 | require 'sinatra/json' 4 | require 'dotenv' 5 | require 'faker' 6 | require 'rack/contrib' 7 | require 'facets/string/snakecase' 8 | 9 | # Load environment configuration 10 | Dotenv.load 11 | 12 | # Set the environment after dotenv loads 13 | # Default to production 14 | set :environment, (ENV['APP_ENV'] || ENV['RACK_ENV'] || :production).to_sym 15 | 16 | # Set public folder 17 | set :public_folder, 'public' 18 | 19 | # Parse JSON Body parts into params 20 | use ::Rack::PostBodyContentTypeParser 21 | 22 | # Render home page 23 | get '/' do 24 | redirect '/index.html' 25 | end 26 | 27 | # Render notify page 28 | get '/notify/' do 29 | redirect '/notify/index.html' 30 | end 31 | 32 | # Render Chat page 33 | get '/chat/' do 34 | redirect '/chat/index.html' 35 | end 36 | 37 | # Render sync page 38 | get '/sync/' do 39 | redirect '/sync/index.html' 40 | end 41 | 42 | # Basic health check - check environment variables have been configured correctly 43 | get '/config' do 44 | content_type :json 45 | { 46 | TWILIO_ACCOUNT_SID: ENV['TWILIO_ACCOUNT_SID'], 47 | TWILIO_NOTIFICATION_SERVICE_SID: ENV['TWILIO_NOTIFICATION_SERVICE_SID'], 48 | TWILIO_API_KEY: ENV['TWILIO_API_KEY'] , 49 | TWILIO_API_SECRET: ENV['TWILIO_API_SECRET'] != '', 50 | TWILIO_CHAT_SERVICE_SID: ENV['TWILIO_CHAT_SERVICE_SID'], 51 | TWILIO_SYNC_SERVICE_SID: ENV['TWILIO_SYNC_SERVICE_SID'] || 'default' 52 | }.to_json 53 | end 54 | 55 | # Generate an Access Token for an application user - it generates a random 56 | # username for the client requesting a token 57 | get '/token' do 58 | # Create a random username for the client 59 | identity = Faker::Internet.user_name 60 | 61 | # Create an access token which we will sign and return to the client 62 | token = generate_token(identity) 63 | 64 | # Generate the token and send to client 65 | json :identity => identity, :token => token 66 | end 67 | 68 | # Generate an Access Token for an application user with the provided identity 69 | get '/token/:identity' do 70 | identity = params[:identity] 71 | 72 | token = generate_token(identity) 73 | 74 | # Generate the token and send to client 75 | json :identity => identity, :token => token 76 | end 77 | 78 | # Generate an Access Token for an application user with the provided identity 79 | post '/token' do 80 | identity = params[:identity] 81 | 82 | token = generate_token(identity) 83 | 84 | # Generate the token and send to client 85 | json :identity => identity, :token => token 86 | end 87 | 88 | # Notify - create a device binding from a POST HTTP request 89 | post '/register' do 90 | 91 | # Authenticate with Twilio 92 | client = Twilio::REST::Client.new( 93 | ENV['TWILIO_API_KEY'], 94 | ENV['TWILIO_API_SECRET'], 95 | ENV['TWILIO_ACCOUNT_SID'] 96 | ) 97 | 98 | # Reference a valid notification service 99 | service = client.notify.services( 100 | ENV['TWILIO_NOTIFICATION_SERVICE_SID'] 101 | ) 102 | 103 | params_hash = snake_case_keys(params) 104 | params_hash = symbolize_keys(params_hash) 105 | 106 | begin 107 | binding = service.bindings.create(params_hash) 108 | response = { 109 | message: 'Binding created!', 110 | } 111 | json response 112 | rescue Twilio::REST::TwilioError => e 113 | puts e.message 114 | status 500 115 | response = { 116 | message: "Failed to create binding: #{e.message}", 117 | error: e.message 118 | } 119 | json response 120 | end 121 | end 122 | 123 | # Notify - send a notification from a POST HTTP request 124 | post '/send-notification' do 125 | 126 | # Authenticate with Twilio 127 | client = Twilio::REST::Client.new( 128 | ENV['TWILIO_API_KEY'], 129 | ENV['TWILIO_API_SECRET'], 130 | ENV['TWILIO_ACCOUNT_SID'] 131 | ) 132 | 133 | # Reference a valid notification service 134 | service = client.notify.services( 135 | ENV['TWILIO_NOTIFICATION_SERVICE_SID'] 136 | ) 137 | 138 | params_hash = snake_case_keys(params) 139 | params_hash = symbolize_keys(params_hash) 140 | 141 | puts params_hash 142 | 143 | begin 144 | binding = service.notifications.create( 145 | body: params_hash['body'], 146 | identity: params_hash['identity'] 147 | ) 148 | response = { 149 | message: 'Notification Sent!', 150 | } 151 | json response 152 | rescue Twilio::REST::TwilioError => e 153 | puts e.message 154 | status 500 155 | response = { 156 | message: "Failed to send notification: #{e.message}", 157 | error: e.message 158 | } 159 | json response 160 | end 161 | end 162 | 163 | def generate_token(identity) 164 | # Create an access token which we will sign and return to the client 165 | token = Twilio::JWT::AccessToken.new ENV['TWILIO_ACCOUNT_SID'], 166 | ENV['TWILIO_API_KEY'], ENV['TWILIO_API_SECRET'], identity: identity 167 | 168 | # Grant the access token Chat capabilities (if available) 169 | if ENV['TWILIO_CHAT_SERVICE_SID'] 170 | 171 | # Create the Chat Grant 172 | grant = Twilio::JWT::AccessToken::ChatGrant.new 173 | grant.service_sid = ENV['TWILIO_CHAT_SERVICE_SID'] 174 | token.add_grant grant 175 | end 176 | 177 | # Create the Sync Grant 178 | sync_grant = Twilio::JWT::AccessToken::SyncGrant.new 179 | sync_grant.service_sid = ENV['TWILIO_SYNC_SERVICE_SID'] || 'default' 180 | token.add_grant sync_grant 181 | 182 | return token.to_jwt 183 | end 184 | 185 | def symbolize_keys(h) 186 | # Use the symbolize names argument of parse to convert String keys to Symbols 187 | return JSON.parse(JSON.generate(h), symbolize_names: true) 188 | end 189 | 190 | def snake_case_keys(h) 191 | newh = Hash.new 192 | h.keys.each do |key| 193 | newh[key.snakecase] = h[key] 194 | end 195 | return newh 196 | end 197 | 198 | # Ensure that the Sync Default Service is provisioned 199 | def provision_sync_default_service() 200 | client = Twilio::REST::Client.new(ENV['TWILIO_API_KEY'], ENV['TWILIO_API_SECRET'], ENV['TWILIO_ACCOUNT_SID']) 201 | client.sync.services('default').fetch 202 | end 203 | provision_sync_default_service 204 | -------------------------------------------------------------------------------- /public/chat/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing:border-box; 3 | } 4 | 5 | html, body { 6 | padding:0; 7 | margin:0; 8 | height:100%; 9 | width:100%; 10 | color:#dedede; 11 | background-color: #849091; 12 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 13 | } 14 | 15 | header { 16 | width:100%; 17 | position:absolute; 18 | text-align:center; 19 | bottom:20px; 20 | } 21 | 22 | header a, header a:visited { 23 | font-size:18px; 24 | color:#dedede; 25 | text-decoration:none; 26 | } 27 | 28 | header a:hover { 29 | text-decoration:underline; 30 | } 31 | 32 | section { 33 | height:70%; 34 | background-color:#2B2B2A; 35 | } 36 | 37 | section input { 38 | display:block; 39 | height:52px; 40 | width:800px; 41 | margin:10px auto; 42 | outline:none; 43 | background-color:transparent; 44 | border:none; 45 | border-bottom:1px solid #2B2B2A; 46 | padding:0; 47 | font-size:42px; 48 | color:#eee; 49 | } 50 | 51 | #messages { 52 | background-color:#232323; 53 | padding:10px; 54 | height:100%; 55 | width:800px; 56 | margin:0 auto; 57 | overflow-y:auto; 58 | } 59 | 60 | #messages p { 61 | margin:5px 0; 62 | padding:0; 63 | } 64 | 65 | .info { 66 | margin:5px 0; 67 | font-style:italic; 68 | } 69 | 70 | .message-container { 71 | margin:5px 0; 72 | color:#fff; 73 | } 74 | 75 | .message-container .username { 76 | display:inline-block; 77 | margin-right:5px; 78 | font-weight:bold; 79 | color:#849091; 80 | } 81 | 82 | .me, .username.me { 83 | font-weight:bold; 84 | color:cyan; 85 | } 86 | 87 | .message-container .username.me { 88 | display:inline-block; 89 | margin-right:5px; 90 | } -------------------------------------------------------------------------------- /public/chat/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Twilio Chat Quickstart 5 | 6 | 7 | 8 | 9 | 10 |
11 | Read the getting started guide 13 | 14 | 15 |
16 | 17 |
18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /public/chat/index.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | // Get handle to the chat div 3 | var $chatWindow = $('#messages'); 4 | 5 | // Our interface to the Chat service 6 | var chatClient; 7 | 8 | // A handle to the "general" chat channel - the one and only channel we 9 | // will have in this sample app 10 | var generalChannel; 11 | 12 | // The server will assign the client a random username - store that value 13 | // here 14 | var username; 15 | 16 | // Helper function to print info messages to the chat window 17 | function print(infoMessage, asHtml) { 18 | var $msg = $('
'); 19 | if (asHtml) { 20 | $msg.html(infoMessage); 21 | } else { 22 | $msg.text(infoMessage); 23 | } 24 | $chatWindow.append($msg); 25 | } 26 | 27 | // Helper function to print chat message to the chat window 28 | function printMessage(fromUser, message) { 29 | var $user = $('').text(fromUser + ':'); 30 | if (fromUser === username) { 31 | $user.addClass('me'); 32 | } 33 | var $message = $('').text(message); 34 | var $container = $('
'); 35 | $container.append($user).append($message); 36 | $chatWindow.append($container); 37 | $chatWindow.scrollTop($chatWindow[0].scrollHeight); 38 | } 39 | 40 | // Alert the user they have been assigned a random username 41 | print('Logging in...'); 42 | 43 | // Get an access token for the current user, passing a username (identity) 44 | $.getJSON('/token', function(data) { 45 | 46 | 47 | // Initialize the Chat client 48 | Twilio.Chat.Client.create(data.token).then(client => { 49 | console.log('Created chat client'); 50 | chatClient = client; 51 | chatClient.getSubscribedChannels().then(createOrJoinGeneralChannel); 52 | 53 | // when the access token is about to expire, refresh it 54 | chatClient.on('tokenAboutToExpire', function() { 55 | refreshToken(username); 56 | }); 57 | 58 | // if the access token already expired, refresh it 59 | chatClient.on('tokenExpired', function() { 60 | refreshToken(username); 61 | }); 62 | 63 | // Alert the user they have been assigned a random username 64 | username = data.identity; 65 | print('You have been assigned a random username of: ' 66 | + '' + username + '', true); 67 | 68 | }).catch(error => { 69 | console.error(error); 70 | print('There was an error creating the chat client:
' + error, true); 71 | print('Please check your .env file.', false); 72 | }); 73 | }); 74 | 75 | function refreshToken(identity) { 76 | console.log('Token about to expire'); 77 | // Make a secure request to your backend to retrieve a refreshed access token. 78 | // Use an authentication mechanism to prevent token exposure to 3rd parties. 79 | $.getJSON('/token/' + identity, function(data) { 80 | console.log('updated token for chat client'); 81 | chatClient.updateToken(data.token); 82 | }); 83 | } 84 | 85 | function createOrJoinGeneralChannel() { 86 | // Get the general chat channel, which is where all the messages are 87 | // sent in this simple application 88 | print('Attempting to join "general" chat channel...'); 89 | chatClient.getChannelByUniqueName('general') 90 | .then(function(channel) { 91 | generalChannel = channel; 92 | console.log('Found general channel:'); 93 | console.log(generalChannel); 94 | setupChannel(); 95 | }).catch(function() { 96 | // If it doesn't exist, let's create it 97 | console.log('Creating general channel'); 98 | chatClient.createChannel({ 99 | uniqueName: 'general', 100 | friendlyName: 'General Chat Channel' 101 | }).then(function(channel) { 102 | console.log('Created general channel:'); 103 | console.log(channel); 104 | generalChannel = channel; 105 | setupChannel(); 106 | }).catch(function(channel) { 107 | console.log('Channel could not be created:'); 108 | console.log(channel); 109 | }); 110 | }); 111 | } 112 | 113 | // Set up channel after it has been found 114 | function setupChannel() { 115 | // Join the general channel 116 | generalChannel.join().then(function(channel) { 117 | print('Joined channel as ' 118 | + '' + username + '.', true); 119 | }); 120 | 121 | // Listen for new messages sent to the channel 122 | generalChannel.on('messageAdded', function(message) { 123 | printMessage(message.author, message.body); 124 | }); 125 | } 126 | 127 | // Send a new message to the general channel 128 | var $input = $('#chat-input'); 129 | $input.on('keydown', function(e) { 130 | 131 | if (e.keyCode == 13) { 132 | if (generalChannel === undefined) { 133 | print('The Chat Service is not configured. Please check your .env file.', false); 134 | return; 135 | } 136 | generalChannel.sendMessage($input.val()) 137 | $input.val(''); 138 | } 139 | }); 140 | }); 141 | -------------------------------------------------------------------------------- /public/config-check.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $.get('/config', function(response) { 3 | Object.keys(fields).forEach(configureField(fields, response)); 4 | Object.keys(buttons).forEach(configureButton(buttons, response)); 5 | }); 6 | 7 | // Button Ids' and Config Keys 8 | var buttons = { 9 | chatDemoButton: 'TwilioChatServiceSid', 10 | syncDemoButton: 'TwilioSyncServiceSid', 11 | notifyDemoButton: 'TwilioNotificationServiceSid' 12 | }; 13 | 14 | // Field Ids' and Masked Flag 15 | var fields = { 16 | twilioAccountSid: false, 17 | twilioApiKey: false, 18 | twilioApiSecret: true, 19 | twilioNotificationServiceSid: false, 20 | twilioChatServiceSid: false, 21 | twilioSyncServiceSid: false 22 | }; 23 | 24 | var configureField = function(fields, response) { 25 | var htmlContent = 'Not configured in .env'; 26 | var cssClass = 'unset'; 27 | return function(fieldId) { 28 | var configKey = strToConfig(fieldId); 29 | var isMasked = fields[fieldId]; 30 | if (!!response[configKey]) { 31 | htmlContent = isMasked ? 'Configured properly' : response[configKey]; 32 | cssClass = 'set'; 33 | } 34 | $('#' + fieldId).html(htmlContent).addClass(cssClass); 35 | }; 36 | }; 37 | 38 | var configureButton = function(buttons, response) { 39 | var hasBasicConfig = !!response.TWILIO_ACCOUNT_SID && 40 | !!response.TWILIO_API_KEY && 41 | !!response.TWILIO_API_SECRET; 42 | return function(buttonId) { 43 | var configKey = strToConfig(buttons[buttonId]); 44 | var cssClass = hasBasicConfig && !!response[configKey] 45 | ? 'btn-success' 46 | : 'btn-danger'; 47 | $('#' + buttonId).addClass(cssClass); 48 | }; 49 | }; 50 | 51 | var strToConfig = function(string) { 52 | return string 53 | .split(/(?=[A-Z])/) 54 | .map(function(e) { return e.toUpperCase(); }) 55 | .join('_'); 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /public/index.css: -------------------------------------------------------------------------------- 1 | .config-value.set { 2 | color:seagreen; 3 | } 4 | 5 | .config-value.unset { 6 | color:darkred; 7 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Twilio Server Starter Kit 5 | 6 | 9 | 10 | 11 | 12 | 13 |
14 |

Twilio Server Starter Kit Environment Setup

15 | 16 |

Account Information

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
TWILIO_ACCOUNT_SID
TWILIO_API_KEY
TWILIO_API_SECRET
31 | 32 |

Products

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
NotifyTWILIO_NOTIFICATION_SERVICE_SID
ChatTWILIO_CHAT_SERVICE_SID
SyncTWILIO_SYNC_SERVICE_SID
50 | 51 |

Demos

52 | Sync 53 | Notify 54 | Chat 55 |
56 | 57 | 58 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /public/notify/facebook_messenger.html: -------------------------------------------------------------------------------- 1 | 2 | 27 |
32 |
33 | 34 | -------------------------------------------------------------------------------- /public/notify/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello App! - Notify Quickstart 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | Read the Twilio Notify guide 13 | 14 |
15 | 16 |
17 |

Send Notification to Identity

18 | 19 | 20 |

21 | 22 | 23 |

Welcome to Notify!
24 | 25 |

After you set up a notification binding, go ahead and send a notification to that identity!

26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /public/notify/notify.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing:border-box; 3 | } 4 | 5 | html, body { 6 | padding:0; 7 | margin:0; 8 | height:100%; 9 | width:100%; 10 | color:#dedede; 11 | background-color: #849091; 12 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 13 | } 14 | 15 | header { 16 | width:100%; 17 | position:absolute; 18 | text-align:center; 19 | bottom:20px; 20 | } 21 | 22 | header a, header a:visited { 23 | font-size:18px; 24 | color:#dedede; 25 | text-decoration:none; 26 | } 27 | 28 | header a:hover { 29 | text-decoration:underline; 30 | } 31 | 32 | section { 33 | background-color:#2B2B2A; 34 | text-align:center; 35 | padding:16px; 36 | } 37 | 38 | #message { 39 | padding:6px; 40 | } 41 | -------------------------------------------------------------------------------- /public/notify/notify.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $('#sendNotificationButton').on('click', function() { 3 | $.post('/send-notification', { 4 | identity: $('#identityInput').val(), 5 | body: "Hello, world!" 6 | }, function(response) { 7 | $('#identityInput').val(''); 8 | $('#message').html(response.message); 9 | console.log(response); 10 | }); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /public/sync/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing:border-box; 3 | } 4 | 5 | html, body { 6 | padding:0; 7 | margin:0; 8 | height:100%; 9 | width:100%; 10 | color:#dedede; 11 | background-color: #849091; 12 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 13 | } 14 | 15 | header { 16 | width:100%; 17 | position:absolute; 18 | text-align:center; 19 | bottom:20px; 20 | } 21 | 22 | header a, header a:visited { 23 | font-size:18px; 24 | color:#dedede; 25 | text-decoration:none; 26 | } 27 | 28 | header a:hover { 29 | text-decoration:underline; 30 | } 31 | 32 | section { 33 | background-color:#2B2B2A; 34 | text-align:center; 35 | padding:16px; 36 | } 37 | 38 | button:hover { 39 | cursor:pointer; 40 | background-color:#000; 41 | color:#fff; 42 | } 43 | 44 | #message { 45 | padding:6px; 46 | } 47 | 48 | #board { 49 | width: 33%; 50 | margin-left: auto; 51 | margin-right: auto; 52 | } 53 | 54 | #board .board-row { 55 | width: 100%; 56 | padding-bottom: 3px; 57 | } 58 | 59 | #board .board-row button { 60 | width: 30%; 61 | height: 100px; 62 | font-size: 50px; 63 | } -------------------------------------------------------------------------------- /public/sync/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tic-Tac-Twilio - Sync Quickstart 5 | 6 | 7 | 8 | 9 | 10 |
11 | 13 | Read the getting started guide 14 | 15 | 16 |
17 | 18 |
19 |

Tic-Tac-Twilio

20 | 21 |
22 |
23 | 24 | 25 | 26 |
27 |
28 | 29 | 30 | 31 |
32 |
33 | 34 | 35 | 36 |
37 |
38 | 39 |
40 | Welcome! Initializing Sync... 41 |
42 | 43 |

Open this page in a few tabs to test!

44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /public/sync/index.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | //We'll use message to tell the user what's happening 3 | var $message = $('#message'); 4 | 5 | //Get handle to the game board buttons 6 | var $buttons = $('#board .board-row button'); 7 | 8 | //Our interface to the Sync service 9 | var syncClient; 10 | 11 | // Every browser Sync client relies on FPA tokens to authenticate and authorize it 12 | // for access to your Sync data. 13 | // 14 | // In this quickstart, we're using our own token generator. You can generate a token 15 | // in any backend language that Twilio supports, or generate a Twilio Function so 16 | // Twilio can host it for you. See the docs for more details. 17 | // 18 | $.getJSON('/token', function (tokenResponse) { 19 | 20 | // Once we have the token, we can initialize the Sync client and start subscribing 21 | // to data. The client will initialize asynchronously while we load the rest of 22 | // the user interface. 23 | // 24 | syncClient = new Twilio.Sync.Client(tokenResponse.token, { logLevel: 'info' }); 25 | syncClient.on('connectionStateChanged', function(state) { 26 | if (state != 'connected') { 27 | $message.html('Sync is not live (websocket connection ' + state + ')…'); 28 | } else { 29 | // Now that we're connected, lets light up our board and play! 30 | $buttons.attr('disabled', false); 31 | $message.html('Sync is live!'); 32 | } 33 | }); 34 | 35 | // Let's pop a message on the screen to show that Sync is working 36 | $message.html('Loading board data…'); 37 | 38 | // Our game state is stored in a Sync document. Here, we'll attach to that document 39 | // (or create it, if it doesn't exist) and connect the necessary event handlers. 40 | // 41 | syncClient.document('SyncGame').then(function(syncDoc) { 42 | var data = syncDoc.value; 43 | if (data.board) { 44 | updateUserInterface(data); 45 | } 46 | 47 | // Any time the board changes, we want to show the new state. The 'updated' 48 | // event is for this. 49 | syncDoc.on('updated', function(event) { 50 | console.debug("Board was updated", event.isLocal? "locally." : "by the other guy."); 51 | updateUserInterface(event.value); 52 | }); 53 | 54 | // Let's make our buttons control the game state in Sync… 55 | $buttons.on('click', function (e) { 56 | // Toggle the value: X, O, or empty 57 | toggleCellValue($(e.target)); 58 | 59 | // Send updated document to Sync. This will trigger "updated" events for all players. 60 | var data = readGameBoardFromUserInterface(); 61 | syncDoc.set(data); 62 | }); 63 | }); 64 | 65 | }); 66 | 67 | //Toggle the value: X, O, or empty (  for UI) 68 | function toggleCellValue($cell) { 69 | var cellValue = $cell.html(); 70 | 71 | if (cellValue === 'X') { 72 | $cell.html('O'); 73 | } else if (cellValue === 'O') { 74 | $cell.html(' '); 75 | } else { 76 | $cell.html('X'); 77 | } 78 | } 79 | 80 | //Read the state of the UI and create a new document 81 | function readGameBoardFromUserInterface() { 82 | var board = [ 83 | ['', '', ''], 84 | ['', '', ''], 85 | ['', '', ''] 86 | ]; 87 | 88 | for (var row = 0; row < 3; row++) { 89 | for (var col = 0; col < 3; col++) { 90 | var selector = '[data-row="' + row + '"]' + 91 | '[data-col="' + col + '"]'; 92 | board[row][col] = $(selector).html().replace(' ', ''); 93 | } 94 | } 95 | 96 | return {board: board}; 97 | } 98 | 99 | //Update the buttons on the board to match our document 100 | function updateUserInterface(data) { 101 | for (var row = 0; row < 3; row++) { 102 | for (var col = 0; col < 3; col++) { 103 | var this_cell = '[data-row="' + row + '"]' + '[data-col="' + col + '"]'; 104 | var cellValue = data.board[row][col]; 105 | $(this_cell).html(cellValue === '' ? ' ' : cellValue); 106 | } 107 | } 108 | } 109 | }); 110 | --------------------------------------------------------------------------------