├── .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 |
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 | 
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 |