├── public ├── images │ ├── heart.gif │ ├── favicon.png │ ├── TwilioQuest32.png │ └── javascript_shield256.png ├── javascripts │ ├── form.js │ └── ui.js └── stylesheets │ └── style.css ├── views ├── error.ejs └── index.ejs ├── package.json ├── LICENSE ├── bin └── www ├── app.js ├── README.md └── .gitignore /public/images/heart.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/starter-node/HEAD/public/images/heart.gif -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/starter-node/HEAD/public/images/favicon.png -------------------------------------------------------------------------------- /views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /public/images/TwilioQuest32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/starter-node/HEAD/public/images/TwilioQuest32.png -------------------------------------------------------------------------------- /public/images/javascript_shield256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/starter-node/HEAD/public/images/javascript_shield256.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starter-node", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "cookie-parser": "~1.4.4", 10 | "debug": "~2.6.9", 11 | "ejs": "~2.6.1", 12 | "express": "~4.16.1", 13 | "http-errors": "~1.6.3", 14 | "morgan": "~1.9.1", 15 | "twilio": "^3.33.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/javascripts/form.js: -------------------------------------------------------------------------------- 1 | // Show an information box at the top of the page 2 | function showFlash(message) { 3 | $('#flash span').html(message); 4 | $('#flash').show(); 5 | } 6 | 7 | // Intercept our form button click 8 | $('form button').on('click', function(e) { 9 | e.preventDefault(); 10 | 11 | // Based on the selected demo, fire off an ajax request 12 | // We expect just a string of text back from the server (keeping it simple) 13 | var url = currentDemo == 'message' ? '/message' : '/call'; 14 | $.ajax(url, { 15 | method:'POST', 16 | dataType:'text', 17 | data:{ 18 | to:$('#to').val() 19 | }, 20 | success: function(data) { 21 | showFlash(data); 22 | }, 23 | error: function(jqxhr) { 24 | alert('There was an error sending a request to the server :('); 25 | } 26 | }) 27 | }); 28 | -------------------------------------------------------------------------------- /public/javascripts/ui.js: -------------------------------------------------------------------------------- 1 | // Track the currently selected demo 2 | var currentDemo = 'message'; 3 | 4 | // Change the currently selected demo 5 | function changeTab(newTab) { 6 | if (newTab === 'message') { 7 | currentDemo = 'message'; 8 | $('#messaging').addClass('current'); 9 | $('#call').removeClass('current'); 10 | $('form button').html('Send me a message'); 11 | } else { 12 | currentDemo = 'call'; 13 | $('#call').addClass('current'); 14 | $('#messaging').removeClass('current'); 15 | $('form button').html('Call my phone'); 16 | } 17 | } 18 | 19 | // Set up handlers for tabs 20 | $('#messaging').on('click', function(e) { 21 | e.preventDefault(); 22 | changeTab('message'); 23 | }); 24 | $('#call').on('click', function(e) { 25 | e.preventDefault(); 26 | changeTab('call'); 27 | }); 28 | 29 | // Set up handler for "flash" message 30 | $('#flash a').on('click', function(e) { 31 | e.preventDefault(); 32 | $('#flash').hide(); 33 | }); 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Kevin Whinnery 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('starter-node:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Welcome to the JavaScript Guild! 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 |
23 | 24 | 27 | 28 |
29 | JavaScript Shield 30 |

JavaScript Guild

31 |

32 | Welcome to the JavaScript Guild! Join your fellow JavaScripters 33 | and JavaScriptresses as they asynchronously dominate this 34 | Twilio quest. 35 |

36 |

37 | Gather your party and 38 | venture forth 39 |

40 |
41 | 42 |

Hello World

43 |

44 | Below, we have two simple demos that will confirm your environment 45 | has been properly configured. Please refer to the 46 | README.md in your 47 | starter app repository to see how to configure this application. 48 |

49 |
50 | 54 |
55 |

Enter your mobile phone number:

56 | 58 | 59 |
60 |
61 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var createError = require('http-errors'); 2 | var express = require('express'); 3 | var path = require('path'); 4 | var cookieParser = require('cookie-parser'); 5 | var logger = require('morgan'); 6 | var twilio = require('twilio'); 7 | 8 | // Load configuration information from system environment variables. 9 | var TWILIO_ACCOUNT_SID = process.env.TWILIO_ACCOUNT_SID, 10 | TWILIO_AUTH_TOKEN = process.env.TWILIO_AUTH_TOKEN, 11 | TWILIO_PHONE_NUMBER = process.env.TWILIO_PHONE_NUMBER; 12 | 13 | // Create an authenticated client to access the Twilio REST API 14 | var client = twilio(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN); 15 | 16 | var app = express(); 17 | 18 | // view engine setup 19 | app.set('views', path.join(__dirname, 'views')); 20 | app.set('view engine', 'ejs'); 21 | 22 | app.use(logger('dev')); 23 | app.use(express.json()); 24 | app.use(express.urlencoded({ extended: false })); 25 | app.use(cookieParser()); 26 | app.use(express.static(path.join(__dirname, 'public'))); 27 | 28 | // render our home page 29 | app.get('/', function(req, res, next) { 30 | res.render('index'); 31 | }); 32 | 33 | // handle a POST request to send a text message. 34 | // This is sent via ajax on our home page 35 | app.post('/message', function(req, res, next) { 36 | // Use the REST client to send a text message 37 | client.messages.create({ 38 | to: req.body.to, 39 | from: TWILIO_PHONE_NUMBER, 40 | body: 'Good luck on your Twilio quest!' 41 | }).then(function(message) { 42 | // When we get a response from Twilio, respond to the HTTP POST request 43 | res.send('Message is inbound!'); 44 | }); 45 | }); 46 | 47 | // handle a POST request to make an outbound call. 48 | // This is sent via ajax on our home page 49 | app.post('/call', function(req, res, next) { 50 | // Use the REST client to send a text message 51 | client.calls.create({ 52 | to: req.body.to, 53 | from: TWILIO_PHONE_NUMBER, 54 | url: 'http://demo.twilio.com/docs/voice.xml' 55 | }).then(function(message) { 56 | // When we get a response from Twilio, respond to the HTTP POST request 57 | res.send('Call incoming!'); 58 | }); 59 | }); 60 | 61 | // Create a TwiML document to provide instructions for an outbound call 62 | app.post('/hello', function(req, res, next) { 63 | // Create a TwiML generator 64 | var twiml = new twilio.twiml.VoiceResponse(); 65 | // var twiml = new twilio.TwimlResponse(); 66 | twiml.say('Hello there! You have successfully configured a web hook.'); 67 | twiml.say('Good luck on your Twilio quest!', { 68 | voice:'woman' 69 | }); 70 | 71 | // Return an XML response to this request 72 | res.set('Content-Type','text/xml'); 73 | res.send(twiml.toString()); 74 | }); 75 | 76 | // catch 404 and forward to error handler 77 | app.use(function(req, res, next) { 78 | next(createError(404)); 79 | }); 80 | 81 | // error handler 82 | app.use(function(err, req, res, next) { 83 | // set locals, only providing error in development 84 | res.locals.message = err.message; 85 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 86 | 87 | // render the error page 88 | res.status(err.status || 500); 89 | res.render('error'); 90 | }); 91 | 92 | module.exports = app; 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to the JavaScript Guild! 2 | 3 | As members of the JavaScript guild, you will be working through the challenges of TwilioQuest using server-side JavaScript, specifically [node.js](http://www.nodejs.org). This project is pre-configured to do some interesting Twilio stuff using node.js and the [Express](http://expressjs.com/) web framework. 4 | 5 | ## Setting Up 6 | 7 | We assume that before you begin, you will have [node.js and npm](http://www.nodejs.org) and installed on your system. Before you can run this project, you will need to set three system environment variables. These are: 8 | 9 | * `TWILIO_ACCOUNT_SID` : Your Twilio "account SID" - it's like your username for the Twilio API. This and the auth token (below) can be found [on the console](https://www.twilio.com/console). 10 | * `TWILIO_AUTH_TOKEN` : Your Twilio "auth token" - it's your password for the Twilio API. This and the account SID (above) can be found [on the console](https://www.twilio.com/console). 11 | * `TWILIO_PHONE_NUMBER` : A Twilio number that you own, that can be used for making calls and sending messages. You can find a list of phone numbers you control (and buy another one, if necessary) [in the console](https://www.twilio.com/console/phone-numbers/incoming). 12 | 13 | For Mac and Linux, environment variables can be set by opening a terminal window and typing the following three commands - replace all the characters after the `=` with values from your Twilio account: 14 | 15 | export TWILIO_ACCOUNT_SID=ACXXXXXXXXX 16 | export TWILIO_AUTH_TOKEN=XXXXXXXXX 17 | export TWILIO_PHONE_NUMBER=+16518675309 18 | 19 | To make these changes persist for every new terminal (on OS X), you can edit the file `~/.bash_profile` to contain the three commands above. This will set these environment variables for every subsequent session. Once you have edited the file to contain these commands, run `source ~/.bash_profile` in the terminal to set up these variables. 20 | 21 | On Windows, the easiest way to set permanent environment variables (as of Windows 8) is using the `setx` command. Note that there is no `=`, just the key and value separated by a space: 22 | 23 | setx TWILIO_ACCOUNT_SID 'ACXXXXXXXXX' 24 | setx TWILIO_AUTH_TOKEN 'XXXXXXXXX' 25 | setx TWILIO_PHONE_NUMBER '+16518675309' 26 | 27 | ## Running the application 28 | 29 | [Download the project source code directly](https://github.com/twilio/starter-node/archive/master.zip) or [clone the repository on GitHub](https://github.com/twilio/starter-node). Navigate to the folder with the source code on your machine in a terminal window. 30 | 31 | You will first need to install the application's dependencies. You can do this using npm, the bundled package manager for node.js: 32 | 33 | npm install 34 | 35 | Now, you should be able to launch the application. From your terminal, run `npm start`. This should launch your Express application on port 3000 - [visit that URL on your local host at http://localhost:3000](http://localhost:3000/). Enter your mobile number in the fields provided, and test both SMS text messages and phone calls being sent to the mobile number you provide. 36 | 37 | 38 | 39 | If your phone receives both a call and text message, you're good to go! 40 | 41 | ## Begin Questing! 42 | This is but your first step into a larger world. [Return to TwilioQuest](http://quest.twilio.com) to continue your adventure. Huzzah! 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig 2 | 3 | # Created by https://www.gitignore.io/api/macos,node,windows,visualstudiocode 4 | # Edit at https://www.gitignore.io/?templates=macos,node,windows,visualstudiocode 5 | 6 | ### macOS ### 7 | # General 8 | .DS_Store 9 | .AppleDouble 10 | .LSOverride 11 | 12 | # Icon must end with two \r 13 | Icon 14 | 15 | # Thumbnails 16 | ._* 17 | 18 | # Files that might appear in the root of a volume 19 | .DocumentRevisions-V100 20 | .fseventsd 21 | .Spotlight-V100 22 | .TemporaryItems 23 | .Trashes 24 | .VolumeIcon.icns 25 | .com.apple.timemachine.donotpresent 26 | 27 | # Directories potentially created on remote AFP share 28 | .AppleDB 29 | .AppleDesktop 30 | Network Trash Folder 31 | Temporary Items 32 | .apdisk 33 | 34 | ### Node ### 35 | # Logs 36 | logs 37 | *.log 38 | npm-debug.log* 39 | yarn-debug.log* 40 | yarn-error.log* 41 | lerna-debug.log* 42 | 43 | # Diagnostic reports (https://nodejs.org/api/report.html) 44 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 45 | 46 | # Runtime data 47 | pids 48 | *.pid 49 | *.seed 50 | *.pid.lock 51 | 52 | # Directory for instrumented libs generated by jscoverage/JSCover 53 | lib-cov 54 | 55 | # Coverage directory used by tools like istanbul 56 | coverage 57 | *.lcov 58 | 59 | # nyc test coverage 60 | .nyc_output 61 | 62 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 63 | .grunt 64 | 65 | # Bower dependency directory (https://bower.io/) 66 | bower_components 67 | 68 | # node-waf configuration 69 | .lock-wscript 70 | 71 | # Compiled binary addons (https://nodejs.org/api/addons.html) 72 | build/Release 73 | 74 | # Dependency directories 75 | node_modules/ 76 | jspm_packages/ 77 | 78 | # TypeScript v1 declaration files 79 | typings/ 80 | 81 | # TypeScript cache 82 | *.tsbuildinfo 83 | 84 | # Optional npm cache directory 85 | .npm 86 | 87 | # Optional eslint cache 88 | .eslintcache 89 | 90 | # Optional REPL history 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | *.tgz 95 | 96 | # Yarn Integrity file 97 | .yarn-integrity 98 | 99 | # dotenv environment variables file 100 | .env 101 | .env.test 102 | 103 | # parcel-bundler cache (https://parceljs.org/) 104 | .cache 105 | 106 | # next.js build output 107 | .next 108 | 109 | # nuxt.js build output 110 | .nuxt 111 | 112 | # react / gatsby 113 | public/ 114 | 115 | # vuepress build output 116 | .vuepress/dist 117 | 118 | # Serverless directories 119 | .serverless/ 120 | 121 | # FuseBox cache 122 | .fusebox/ 123 | 124 | # DynamoDB Local files 125 | .dynamodb/ 126 | 127 | ### VisualStudioCode ### 128 | .vscode/* 129 | # The settins file is personal and it's a team choice 130 | # to force people to check it in and use it 131 | # !.vscode/settings.json 132 | !.vscode/tasks.json 133 | !.vscode/launch.json 134 | !.vscode/extensions.json 135 | 136 | ### VisualStudioCode Patch ### 137 | # Ignore all local history of files 138 | .history 139 | 140 | ### Windows ### 141 | # Windows thumbnail cache files 142 | Thumbs.db 143 | Thumbs.db:encryptable 144 | ehthumbs.db 145 | ehthumbs_vista.db 146 | 147 | # Dump file 148 | *.stackdump 149 | 150 | # Folder config file 151 | [Dd]esktop.ini 152 | 153 | # Recycle Bin used on file shares 154 | $RECYCLE.BIN/ 155 | 156 | # Windows Installer files 157 | *.cab 158 | *.msi 159 | *.msix 160 | *.msm 161 | *.msp 162 | 163 | # Windows shortcuts 164 | *.lnk 165 | 166 | # End of https://www.gitignore.io/api/macos,node,windows,visualstudiocode 167 | 168 | # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) 169 | # Anything env does not get committed 170 | *.env 171 | 172 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | /* Eric Meyer reset */ 2 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0} 3 | 4 | /* Global Styles */ 5 | html, body { 6 | background-color:black; 7 | } 8 | 9 | html, body, * { 10 | font-family:"Press Start 2P"; 11 | font-size:18px; 12 | color:white; 13 | } 14 | 15 | form { 16 | text-align:center; 17 | } 18 | 19 | h1 { 20 | font-size: 28px; 21 | margin:10px 0; 22 | line-height: 1.3em; 23 | } 24 | 25 | h3 { 26 | font-size:18px; 27 | margin:10px 0; 28 | color:red; 29 | } 30 | 31 | pre { 32 | font-family: Monaco, monospace, sans-serif; 33 | font-size:12px; 34 | padding:10px; 35 | background-color:#232323; 36 | border:1px #fff solid; 37 | margin-bottom: 10px; 38 | } 39 | 40 | p { 41 | line-height: 1.5em; 42 | font-size:14px; 43 | margin-bottom: 10px; 44 | } 45 | 46 | a { 47 | color:orange; 48 | } 49 | 50 | b { 51 | color:yellow; 52 | font-weight:bold; 53 | } 54 | 55 | input[type=text], input[type=password], input[type=email] { 56 | background-color:black; 57 | border:5px #787878 dashed; 58 | padding:10px; 59 | } 60 | 61 | input[type=text]:focus, input[type=password]:focus, input[type=email]:focus { 62 | outline:none; 63 | } 64 | 65 | button, input[type=button], input[type=submit] { 66 | margin-top: 10px; 67 | text-align: center; 68 | border:8px solid; 69 | background-color: #000; 70 | padding:10px; 71 | color:#fff; 72 | } 73 | 74 | button:hover, input[type=button]:hover, input[type=submit]:hover { 75 | border-style:dashed; 76 | cursor:pointer; 77 | } 78 | 79 | .padding10 { 80 | padding:10px; 81 | } 82 | 83 | #content { 84 | width:900px; 85 | margin:20px auto 10px auto; 86 | } 87 | 88 | #flash { 89 | color:yellow; 90 | text-align:center; 91 | border:2px dashed; 92 | margin-bottom:20px; 93 | padding:10px; 94 | } 95 | 96 | #flash p, #flash a { 97 | color:yellow; 98 | margin:0; 99 | } 100 | 101 | #tabs { 102 | list-style:none; 103 | margin-bottom:40px; 104 | text-align: center; 105 | } 106 | 107 | #tabs li { 108 | display:inline; 109 | cursor:pointer; 110 | padding:10px; 111 | margin-right:20px; 112 | color:gray; 113 | } 114 | 115 | #tabs li.current { 116 | background-color: red; 117 | color:white; 118 | } 119 | 120 | #call-demo { 121 | display:none; 122 | } 123 | 124 | #welcome { 125 | float:left; 126 | width:300px; 127 | text-align:center; 128 | margin:0 20px 20px 0; 129 | } 130 | 131 | #welcome h1 span { 132 | color:yellow; 133 | } 134 | 135 | #footer { 136 | clear:both; 137 | width:900px; 138 | font-size:16px; 139 | text-align: center; 140 | margin:0 auto 10px auto; 141 | padding:50px 10px 10px 10px; 142 | } 143 | 144 | #footer span { 145 | color:red; 146 | } 147 | 148 | #footer p { 149 | font-size:12px; 150 | margin-top:10px; 151 | } 152 | 153 | #footer img { 154 | margin-bottom:-10px; 155 | } --------------------------------------------------------------------------------