├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── RELEASING.md ├── UPGRADING.md ├── dangerfile.js ├── examples ├── apps │ ├── hello_world │ │ ├── index.js │ │ └── package.json │ └── number_guessing_game │ │ ├── index.js │ │ └── package.json ├── public_html │ └── index.html ├── server.js ├── server │ └── login.js └── sslcert │ ├── cert.ca_bundle │ ├── cert.cer │ └── private-key.pem ├── index.js ├── invalid_examples ├── apps │ ├── bad_app_bad_json │ │ ├── index.js │ │ └── package.json │ ├── bad_app_bad_request_json │ │ ├── index.js │ │ └── package.json │ ├── bad_app_name_mismatch │ │ ├── index.js │ │ └── package.json │ ├── bad_app_no_json │ │ └── index.js │ ├── bad_app_not_alexa_app │ │ ├── index.js │ │ └── package.json │ └── hello_world │ │ ├── index.js │ │ └── package.json ├── public_html │ └── index.html ├── server.js ├── server │ └── login.js └── sslcert │ ├── cert.ca_bundle │ ├── cert.cer │ └── private-key.pem ├── package-lock.json ├── package.json ├── test ├── example.json ├── example.txt ├── sample-launch-req.json ├── sample-schema.txt ├── sample-utterances.txt ├── test-examples-server-app-loading-fail-checks.js ├── test-examples-server-custom-bindings.js ├── test-examples-server-https-fail-checks.js ├── test-examples-server-https-support-extended.js ├── test-examples-server-https-support.js ├── test-examples-server-pre-post-functions.js ├── test-examples-server-verification.js ├── test-examples-server.js ├── test-server-config.js └── test-utils.js ├── utils.js └── views ├── angular.min.js ├── templates.json └── test.ejs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | node_modules 21 | 22 | # ========================= 23 | # Operating System Files 24 | # ========================= 25 | 26 | # OSX 27 | # ========================= 28 | 29 | .DS_Store 30 | .AppleDouble 31 | .LSOverride 32 | 33 | # Thumbnails 34 | ._* 35 | 36 | # Files that might appear on external disk 37 | .Spotlight-V100 38 | .Trashes 39 | 40 | # Directories potentially created on remote AFP share 41 | .AppleDB 42 | .AppleDesktop 43 | Network Trash Folder 44 | Temporary Items 45 | .apdisk 46 | /.idea 47 | 48 | # Code Coverage 49 | coverage 50 | 51 | # IDEs 52 | .vs 53 | .vscode 54 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | sudo: false 4 | 5 | matrix: 6 | include: 7 | - node_js: node 8 | script: 9 | - npm run-script danger 10 | - npm run-script coverage 11 | after_script: 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | 14 | node_js: 15 | - 4 16 | - 5 17 | - 6 18 | - 8 19 | - 9 20 | 21 | cache: 22 | directories: 23 | - node_modules 24 | 25 | before_install: 26 | - npm config set spin false 27 | 28 | notifications: 29 | email: false 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### 3.0.3 (Next) 4 | 5 | * Your contribution here. 6 | 7 | ### 3.0.2 (July 12, 2018) 8 | 9 | * [#88](https://github.com/alexa-js/alexa-app-server/pull/88): No longer compatible with Node 0.12 - [@martincostello](https://github.com/martincostello). 10 | * [#88](https://github.com/alexa-js/alexa-app-server/pull/88): Added access token, consent token, device ID and API endpoint to test page - [@martincostello](https://github.com/martincostello). 11 | * [#84](https://github.com/alexa-js/alexa-app-server/pull/84), [#73](https://github.com/alexa-js/alexa-app-server/issues/73): Changed default server_root to `__dirname` - [@benedekh](https://github.com/benedekh). 12 | * [#83](https://github.com/alexa-js/alexa-app-server/pull/83), [#77](https://github.com/alexa-js/alexa-app-server/issues/77): Fixed Application ID not getting set in tests - [@benedekh](https://github.com/benedekh). 13 | 14 | ### 3.0.1 (March 7, 2017) 15 | 16 | * [#71](https://github.com/alexa-js/alexa-app-server/pull/71), [#68](https://github.com/alexa-js/alexa-app-server/issues/68): Fixed log output containing multiple slashes - [@tejashah88](https://github.com/tejashah88). 17 | * [#72](https://github.com/alexa-js/alexa-app-server/pull/72): Used `path.join` for constructing relative paths - [@dblock](https://github.com/dblock). 18 | * [#74](https://github.com/alexa-js/alexa-app-server/pull/74): Added locale selector to test page - [@siedi](https://github.com/siedi). 19 | * [#76](https://github.com/alexa-js/alexa-app-server/pull/76): Changed endpoint message to use app name to match route - [@zweiler](https://github.com/zweiler). 20 | * [#79](https://github.com/alexa-js/alexa-app-server/pull/77): Removed router from `app.express()` configuration options - [@rickwargo](https://github.com/rickwargo). 21 | 22 | ### 3.0.0 (February 6, 2017) 23 | 24 | * [#35](https://github.com/alexa-js/alexa-app-server/issues/35): Removed `body-parser`, properly mounted by `alexa-app` - [@dblock](https://github.com/dblock). 25 | * [#21](https://github.com/alexa-js/alexa-app-server/issues/21), [52](https://github.com/alexa-js/alexa-app-server/issues/52): Setting `verify: true` hangs for requests with signature - [@mreinstein](https://github.com/mreinstein), [@dblock](https://github.com/dblock). 26 | * [#61](https://github.com/alexa-js/alexa-app-server/pull/61): Fix: error occurs if HTTP and HTTPs ports specified are the same - [@dblock](https://github.com/dblock). 27 | * [#60](https://github.com/alexa-js/alexa-app-server/pull/60): Added `httpEnabled` that disables HTTP - [@dblock](https://github.com/dblock). 28 | * [#48](https://github.com/alexa-js/alexa-app-server/pull/48): Removed deprecated dependency `supertest-as-promised` - [@tejashah88](https://github.com/tejashah88). 29 | * [#51](https://github.com/alexa-js/alexa-app-server/pull/51): Enable `strictHeaderCheck` in verifier middleware [#50](https://github.com/alexa-js/alexa-app-server/issues/50) - [@mreinstein](https://github.com/mreinstein). 30 | * [#45](https://github.com/alexa-js/alexa-app-server/pull/45), [#17](https://github.com/alexa-js/alexa-app-server/pull/17): Added support for CA chain certificates - [@tejashah88](https://github.com/tejashah88). 31 | * [#45](https://github.com/alexa-js/alexa-app-server/pull/45): Added option to specify passphrase for unlocking specified SSL files - [@tejashah88](https://github.com/tejashah88). 32 | * [#45](https://github.com/alexa-js/alexa-app-server/pull/45): Added npm command to examine test coverage locally - [@tejashah88](https://github.com/tejashah88). 33 | * [#22](https://github.com/alexa-js/alexa-app-server/pull/22): Adding context object to request templates - [@pwbrown](https://github.com/pwbrown). 34 | * [#43](https://github.com/alexa-js/alexa-app-server/pull/43): Test fixes and an actual test for firing pre/post events. - [@dblock](https://github.com/dblock). 35 | * [#37](https://github.com/alexa-js/alexa-app-server/pull/37): Added `host` option to specify host address to bind servers to - [@tejashah88](https://github.com/tejashah88). 36 | * [#36](https://github.com/alexa-js/alexa-app-server/pull/36): Error occurs when `verify` and `debug` are both set to true - [@tejashah88](https://github.com/tejashah88). 37 | * [#34](https://github.com/alexa-js/alexa-app-server/pull/34): Added tests for fail cases and schemas/utterances - [@tejashah88](https://github.com/tejashah88). 38 | * [#34](https://github.com/alexa-js/alexa-app-server/pull/34): Fixed bug while trying to retrieve schema/utterances with GET request - [@tejashah88](https://github.com/tejashah88). 39 | * [#34](https://github.com/alexa-js/alexa-app-server/pull/34): Updated messages that display an error to be displayed by `self.error` - [@tejashah88](https://github.com/tejashah88). 40 | * [#34](https://github.com/alexa-js/alexa-app-server/pull/34): Optimized some tests to not always start a new server instance after every test - [@tejashah88](https://github.com/tejashah88). 41 | * [#33](https://github.com/alexa-js/alexa-app-server/pull/33): Fix: Express.js deprecation warning - [@tejashah88](https://github.com/tejashah88). 42 | * [#32](https://github.com/alexa-js/alexa-app-server/pull/32): Added tests for request verification, HTTPS support, and POST-based routes - [@tejashah88](https://github.com/tejashah88). 43 | * [#32](https://github.com/alexa-js/alexa-app-server/pull/32): Fix: potential memory leaks from not closing the HTTPS server instance and not removing the hotswap listeners - [@tejashah88](https://github.com/tejashah88). 44 | * [#32](https://github.com/alexa-js/alexa-app-server/pull/32): Use `alexa-verifier-middleware` for request verification - [@tejashah88](https://github.com/tejashah88). 45 | * [#32](https://github.com/alexa-js/alexa-app-server/pull/32): Moved `sslcert` folder into examples - [@tejashah88](https://github.com/tejashah88). 46 | * [#32](https://github.com/alexa-js/alexa-app-server/pull/32): Updated documentation for generating the SSL certificate - [@tejashah88](https://github.com/tejashah88). 47 | * [#28](https://github.com/alexa-js/alexa-app-server/pull/28): Moved to the [alexa-js organization](https://github.com/alexa-js) - [@dblock](https://github.com/dblock). 48 | * [#23](https://github.com/alexa-js/alexa-app-server/pull/23): Added `server.stop()` - [@dblock](https://github.com/dblock). 49 | * [#23](https://github.com/alexa-js/alexa-app-server/pull/23): Added tests, code coverage and LICENSE - [@dblock](https://github.com/dblock). 50 | * [#27](https://github.com/alexa-js/alexa-app-server/pull/27): Added Danger, PR linter - [@dblock](https://github.com/dblock). 51 | 52 | ### 2.3.1 (December 3, 2016) 53 | 54 | * Fixed bug caused by custom slot types call - [@matt-kruse](https://github.com/matt-kruse). 55 | 56 | ### 2.3.0 (November 28, 2016) 57 | 58 | * [#2](https://github.com/alexa-js/alexa-app-server/pull/2): Added view for custom slot types - [@rickwargo](https://github.com/rickwargo). 59 | * [#14](https://github.com/alexa-js/alexa-app-server/pull/2): Added support for alexa-verifier - [@TomMettam](https://github.com/TomMettam). 60 | 61 | ### 2.2.4 (September 13, 2015) 62 | 63 | * [#1](https://github.com/alexa-js/alexa-app-server/pull/1): Added HTTPS Support - [@parisbutterfield](https://github.com/parisbutterfield). 64 | 65 | ### 2.2.3 (August 19, 2015) 66 | 67 | * Added the ability to retrieve schema and utterances output directly using url parameters - [@matt-kruse](https://github.com/matt-kruse). 68 | 69 | ### 2.2.2 (August 18, 2015) 70 | 71 | * Changed `preRequest()` and `postRequest()` to allow them to return a `Promise` if they perform async operations - [@matt-kruse](https://github.com/matt-kruse). 72 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to alexa-app-server 2 | 3 | You're encouraged to submit [pull requests](https://github.com/alexa-js/alexa-app-server/pulls), [propose features and discuss issues](https://github.com/alexa-js/alexa-app-server/issues). 4 | 5 | In the examples below, substitute your Github username for `contributor` in URLs. 6 | 7 | ### Fork the Project 8 | 9 | Fork the [project on Github](https://github.com/alexa-js/alexa-app-server) and check out your copy. 10 | 11 | ``` 12 | git clone https://github.com/contributor/alexa-app-server.git 13 | cd alexa-app-server 14 | git remote add upstream https://github.com/alexa-js/alexa-app-server.git 15 | ``` 16 | 17 | ### Run Tests 18 | 19 | Ensure that you can build the project and run tests. 20 | 21 | ``` 22 | npm install 23 | npm test 24 | ``` 25 | 26 | ## Contribute Code 27 | 28 | ### Create a Topic Branch 29 | 30 | Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. 31 | 32 | ``` 33 | git checkout master 34 | git pull upstream master 35 | git checkout -b my-feature-branch 36 | ``` 37 | 38 | ### Write Tests 39 | 40 | Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Tests live under [test](test). 41 | 42 | We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. 43 | 44 | ### Write Code 45 | 46 | Implement your feature or bug fix. 47 | 48 | Make sure that `npm test` completes without errors. 49 | 50 | ### Write Documentation 51 | 52 | Document any external behavior in the [README](README.md). 53 | 54 | ### Commit Changes 55 | 56 | Make sure git knows your name and email address: 57 | 58 | ``` 59 | git config --global user.name "Your Name" 60 | git config --global user.email "contributor@example.com" 61 | ``` 62 | 63 | Writing good commit logs is important. A commit log should describe what changed and why. 64 | 65 | ``` 66 | git add ... 67 | git commit 68 | ``` 69 | 70 | ### Push 71 | 72 | ``` 73 | git push origin my-feature-branch 74 | ``` 75 | 76 | ### Make a Pull Request 77 | 78 | Go to https://github.com/alexa-js/alexa-app-server and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. 79 | 80 | Add more commits or amend your previous commit with any changes. 81 | 82 | ``` 83 | git commit --amend 84 | git push origin my-feature-branch -f 85 | ``` 86 | 87 | ### Rebase 88 | 89 | If you've been working on a change for a while, rebase with upstream/master. 90 | 91 | ``` 92 | git fetch upstream 93 | git rebase upstream/master 94 | git push origin my-feature-branch -f 95 | ``` 96 | 97 | ### Check on Your Pull Request 98 | 99 | Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above. 100 | 101 | ### Be Patient 102 | 103 | It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there! 104 | 105 | ## Thank You 106 | 107 | Please do know that we really appreciate and value your time and work. We love you, really. 108 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2017 Matt Kruse 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 | # alexa-app-server 2 | 3 | An Alexa App (Skill) Server module using Node.js and the [alexa-app](https://www.npmjs.com/package/alexa-app) module. 4 | 5 | [![NPM](https://img.shields.io/npm/v/alexa-app-server.svg)](https://www.npmjs.com/package/alexa-app-server/) 6 | [![Build Status](https://travis-ci.org/alexa-js/alexa-app-server.svg?branch=master)](https://travis-ci.org/alexa-js/alexa-app-server) 7 | [![Coverage Status](https://coveralls.io/repos/github/alexa-js/alexa-app-server/badge.svg?branch=master)](https://coveralls.io/github/alexa-js/alexa-app-server?branch=master) 8 | 9 | ## Stable Release 10 | 11 | You're reading the documentation for the next release of alexa-app-server, which should be 3.0.3. Please see [CHANGELOG](CHANGELOG.md) and make sure to read [UPGRADING](UPGRADING.md) when upgrading from a previous version. The current stable release is [3.0.2](https://github.com/alexa-js/alexa-app-server/tree/v3.0.2). 12 | 13 | ## Installation 14 | 15 | ``` 16 | npm install alexa-app-server --save 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```javascript 22 | var AlexaAppServer = require('alexa-app-server'); 23 | 24 | var instance = AlexaAppServer.start({ 25 | server_root: __dirname, // Path to root 26 | public_html: "public_html", // Static content 27 | app_dir: "apps", // Location of alexa-app modules 28 | app_root: "/alexa/", // Service root 29 | port: 8080 // Port to use 30 | }); 31 | 32 | instance.stop(); // Stop the server 33 | ``` 34 | 35 | ## Summary 36 | 37 | The alexa-app-server module offers a stand-alone web server to host Alexa Apps (Skills). Think of it as a simple "container" that allows you to easily publish multiple Alexa Apps with one server. This module does not help you write the Alexa Apps (Skills) themselves, as apps are independent modules, written using the [alexa-app](https://www.npmjs.com/package/alexa-app) framework. 38 | 39 | The server can also serve static website content, and offers a built-in Alexa App debugger/simulator. This allows you to test your skill using a web browser and view the responses, without actually using an Amazon Echo. 40 | 41 | ## Key Features 42 | 43 | - Multiple apps can be hosted on a single server 44 | - Apps are stored in the /apps directory by default 45 | - Each app is a stand-alone Node module, built using the alexa-app framework 46 | - Each app must export its alexa-app instance to be loaded into the server 47 | - package.json contains information about the app, including (optionally) the appId 48 | - The hotswap module reloads code changes to apps, if they set `module.change_code = 1` 49 | - Built-in Echo Simulator 50 | - Debug apps by issuing a GET request to the app endpoints 51 | - Send simulated requests to your app, view the JSON response 52 | - Session variables are automatically maintained between requests 53 | - Send intent requests and set slot values 54 | - View generated schema and utterances 55 | - Supports HTTPs 56 | 57 | ## Starting The Server 58 | 59 | You can either get a reference to an `AlexaAppServer` instance, or you can use the `start()` method shortcut. Getting a reference allows you to inspect or change the server object later. 60 | 61 | ```javascript 62 | var AlexaAppServer = require('alexa-app-server'); 63 | var server = new AlexaAppServer({ port: 80, debug: false }); 64 | server.start(); 65 | server.express.use('/test', function(req, res) { res.send("OK"); }); 66 | ``` 67 | 68 | ```javascript 69 | var AlexaAppServer = require('alexa-app-server'); 70 | AlexaAppServer.start({ port: 8080 }); 71 | ``` 72 | 73 | ## Configuration Options 74 | 75 | The `start()` method accepts a configuration object. The defaults are shown below. 76 | 77 | ```javascript 78 | require('alexa-app-server').start({ 79 | 80 | // In order to start the server from a working directory other than 81 | // where your server.js file, you need to provide Node the full path 82 | // to your server's root directory. 83 | // Default is __dirname. 84 | server_root: __dirname, 85 | 86 | // A directory containing static content to serve as the document root. 87 | // This directory is relative to the script using alexa-app-server, not 88 | // relative to the module directory. 89 | // Default is 'public_html'. 90 | public_html: 'public_html', 91 | 92 | // A directory containing Alexa Apps. This directory should contain one 93 | // or more subdirectories. Each subdirectory is a stand-alone Alexa App 94 | // built with the alexa-app framework. These directories are each 95 | // processed during server startup and hooked into the server. 96 | // Default is 'apps'. 97 | app_dir: 'apps', 98 | 99 | // The prefix to use for all Alexa Apps. For example, you may want all 100 | // your Alexa endpoints to be accessed under the "/api/" path off the 101 | // root of your web server. 102 | // Default is 'alexa'. 103 | app_root: 'alexa', 104 | 105 | // The directory containing server-side processing modules. 106 | // Default is 'server'. 107 | server_dir: 'server', 108 | 109 | // Enable http support. 110 | // Default is true. 111 | httpEnabled: true, 112 | 113 | // The port the server should bind to. 114 | // Default is 8080. 115 | port: 8080, 116 | 117 | // The host address in which the server should bind to. 118 | // By default, the host is omitted and the server will accept connections on 119 | // any IPv6 address (::) when IPv6 is available, or any IPv4 address (0.0.0.0) otherwise. 120 | host: '127.0.0.1', 121 | 122 | // Show debugger UI with GET requests to Alexa App endpoints. 123 | // Note that the 'verify' and 'debug' options cannot be used together. 124 | // Default is true. 125 | debug: true, 126 | 127 | // Log useful information with console.log(). 128 | // Default is true. 129 | log: true, 130 | 131 | // Insert alexa-verifier-middleware and add verification for Alexa requests 132 | // as required by the Alexa certification process. 133 | // Default is false. 134 | verify: false, 135 | 136 | // The pre() method is called after the express server has been instantiated, but 137 | // before any Alexa Apps have been loaded. It is passed the AlexaAppServer object itself. 138 | pre: function(appServer) { }, 139 | 140 | // The post() method is called after the server has started and the start() method 141 | // is ready to exit. It's passed the AlexaAppServer object itself. 142 | post: function(appServer) { }, 143 | 144 | // Like pre(), but this function is fired on every request, but before the 145 | // application itself gets called. You can use this to load up user details before 146 | // every request, for example, and insert it into the json request itself for 147 | // the application to use. 148 | // If it returns a falsy value, the request json is not changed. 149 | // If it returns a non-falsy value, the request json is replaced with what was returned. 150 | // If it returns a Promise, request processing pauses until the Promise resolves. 151 | // The value passed on by the promise (if any) replaces the request json. 152 | preRequest: function(json, request, response) { }, 153 | 154 | // Like post(), but this function is fired after every request. It has a final 155 | // opportunity to modify the JSON response before it is returned back to the 156 | // Alexa service. 157 | // If it returns a falsy value, the response json is not changed. 158 | // If it returns a non-falsy value, the response json is replaced with what was returned. 159 | // If it returns a Promise, response processing pauses until the Promise resolves. 160 | // The value passed on by the promise (if any) replaces the response json. 161 | postRequest : function(json, request, response) { }, 162 | 163 | // Enable https support. Note httpsPort, privateKey, and certificate are required. 164 | // Default is false. 165 | httpsEnabled: false, 166 | 167 | // The https port the server will bind to. Required for httpsEnabled support. 168 | // Default is undefined. 169 | httpsPort: 443, 170 | 171 | // The private key filename. This file must reside in the sslcert folder under the 172 | // root of the project. 173 | // Default is undefined. 174 | privateKey: 'private-key.pem', 175 | 176 | // The certificate filename. This file must reside in the sslcert folder under the root of the 177 | // project. 178 | // Default is undefined. 179 | certificate: 'cert.cer', 180 | 181 | // The certificate chain bundle filename. This is an optional file that must reside in the 182 | // sslcert folder under the root of the project. 183 | // Default is undefined. 184 | chain: 'cert.ca_bundle', 185 | 186 | // An optional passphrase used to validate certificate and key files. For best practice, don't 187 | // put the password directly in your source code, especially if it's going to be on GitHub, and 188 | // instead, load it from process.env or a file included in the .gitignore list. 189 | // Default is undefined. 190 | passphrase: 'passphrase' 191 | 192 | }); 193 | ``` 194 | 195 | ## Enabling HTTPs 196 | 197 | You can use a PaaS, such as Heroku, which comes with SSL enabled out-of-the-box. 198 | 199 | Alternatively, you can enable HTTPs support using the instructions below. 200 | 201 | Generate a x509 SSL Certificate using the following: 202 | 203 | ```bash 204 | openssl genrsa -out private-key.pem 1024 205 | openssl req -new -x509 -key private-key.pem -out cert.cer -days 365 206 | ``` 207 | 208 | To make sure the certificate is verified, use the following: 209 | 210 | ```bash 211 | openssl x509 -noout -text -in cert.cer 212 | ``` 213 | 214 | Place the two generated files in the sslcert directory. 215 | 216 | Add the following properties the to config that creates the server. 217 | 218 | ```javascript 219 | AlexaAppServer.start({ 220 | httpsPort: 443, 221 | httpsEnabled: true, 222 | privateKey: 'private-key.pem', 223 | certificate: 'cert.cer' 224 | }); 225 | ``` 226 | 227 | ## Debugging With The Echo Simulator 228 | 229 | Each app (skill) is available at a url endpoint on the server, and responds to POST requests from the Echo. If you load an app's endpoint in your browser with a GET request, it will display an echo simulator that can be used to debug your application. With it, you can send different request types to your app, load slots with values you specify, etc and see the actual generated JSON output from your application. 230 | 231 | ### Show Application ID 232 | 233 | To show the application ID in the session correctly, set `applicationId` in `package.json`. 234 | 235 | ```javascript 236 | { 237 | "alexa": { 238 | "applicationId": "amzn1.echo-sdk-ams.app.999999-d0ed-9999-ad00-999999d00ebe" 239 | } 240 | } 241 | ``` 242 | 243 | Assign the value in your alexa-app. 244 | 245 | ```javascript 246 | var app = new alexa.app('hello_world'); 247 | app.id = require('./package.json').alexa.applicationId; 248 | ``` 249 | 250 | ## View Generated Schema And Utterances 251 | 252 | In the Echo Simulator, your application's schema definition and example utterances are displayed. These can be directly pasted into the Amazon Developer interface when defining your skill. 253 | 254 | You can also get the schema and utterances directly from your endpoint url using url parameters: 255 | ``` 256 | GET /your/app/endpoint?schema 257 | GET /your/app/endpoint?utterances 258 | ``` 259 | 260 | ## Dynamic Server-Side Functionality 261 | 262 | Most servers will need some server-side processing logic, such as handling logins, or processing forms. You can specify a directory containing files that define server-side functionality by hooking into Express. These files are stand-alone modules that export a single function that the framework calls. An example is below and in the "examples/server" directory. 263 | 264 | The default directory used to hold these modules is "server" but you can change this by using the "server_dir" configuration parameter, as shown above. 265 | 266 | For example, [examples/server/login.js](examples/server/login.js): 267 | 268 | ```javascript 269 | module.exports = function(express, alexaAppServerObject) { 270 | express.use('/login', function(req, res) { 271 | res.send("imagine this is a dynamic server-side login action"); 272 | }); 273 | }; 274 | ``` 275 | 276 | ## Sample App Structure 277 | 278 | This is a sample directory structure of what a complete app server might look like. 279 | 280 | ``` 281 | . 282 | +--- server.js 283 | +--- sslcert 284 | +--- apps 285 | +--- alexa-app-1 286 | +--- package.json 287 | +--- index.js 288 | +--- node_modules 289 | +--- alexa-app-2 290 | +--- package.json 291 | +--- index.js 292 | +--- node_modules 293 | +--- public_html 294 | +--- index.html 295 | ``` 296 | 297 | ## Running in Production 298 | 299 | While individual `alexa-app` functions can be deployed to AWS Lambda, the `alexa-app-server` module can be used in both development and production for multiple applications. It will work with the Alexa Service Simulator on [developer.amazon.com](https://developer.amazon.com), a real Echo device, etc. 300 | 301 | Choose `HTTPs` in _Service Endpoint Type_ in the Alexa app configuration on [developer.amazon.com](https://developer.amazon.com) and point to one of your apps. For example, [alexa-app-server-hello-world](https://github.com/dblock/alexa-app-server-hello-world) is available at `https://alexa-app-server-hello-world.herokuapp.com/alexa/hello_world`. 302 | 303 | Make sure to set `verify: true` and `debug: false` in production environments. 304 | 305 | ## Examples 306 | 307 | See the [example application in the "examples" directory](examples). 308 | 309 | # History 310 | 311 | See [CHANGELOG](CHANGELOG.md) for details. 312 | 313 | # License 314 | 315 | Copyright (c) 2016-2017 Matt Kruse 316 | 317 | MIT License, see [LICENSE](LICENSE.md) for details. 318 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | Releasing 2 | ========= 3 | 4 | There're no particular rules about when to release alexa-app-server. Release bug fixes frequently, features not so frequently and breaking API changes rarely. 5 | 6 | ### Release 7 | 8 | Run tests, check that all tests succeed locally. 9 | 10 | ``` 11 | npm install 12 | npm test 13 | ``` 14 | 15 | Check that the last build succeeded in [Travis CI](https://travis-ci.org/alexa-js/alexa-app-server). 16 | 17 | Those with r/w permissions to the [master alexa-app-server repository](https://github.com/alexa-js/alexa-app-server) generally have alexa-app-server-based projects. Point one to alexa-app-server HEAD and run all your tests to catch any obvious regressions. 18 | 19 | ``` 20 | "dependencies": { 21 | "alexa-app-server": "alexa-js/alexa-app-server" 22 | } 23 | ``` 24 | 25 | Modify the "Stable Release" section in [README.md](README.md). Change the text to reflect that this is going to be the documentation for a stable release. Remove references to the previous release of alexa-app-server. Keep the file open, you'll have to undo this change after the release. 26 | 27 | ``` 28 | ## Stable Release 29 | 30 | You're reading the documentation for the stable release of alexa-app-server, 3.0.1. 31 | ``` 32 | 33 | Change "Next Release" in [CHANGELOG.md](CHANGELOG.md) to the new version. 34 | 35 | ``` 36 | ### 3.0.1 (Feb 7, 2017) 37 | ``` 38 | 39 | Remove the line with "Your contribution here.", since there will be no more contributions to this release. 40 | 41 | Commit your changes. 42 | 43 | ``` 44 | git add README.md CHANGELOG.md 45 | git commit -m "Preparing for release, 3.0.1." 46 | ``` 47 | 48 | Tag the release. 49 | 50 | ``` 51 | git tag v3.0.1 52 | ``` 53 | 54 | Release. 55 | 56 | ``` 57 | $ npm publish 58 | ``` 59 | 60 | Push. 61 | 62 | ``` 63 | $ git push --tags 64 | ``` 65 | 66 | ### Prepare for the Next Developer Iteration 67 | 68 | Modify the "Stable Release" section in [README.md](README.md). Change the text to reflect that this is going to be the next release. 69 | 70 | ``` 71 | ## Stable Release 72 | 73 | You're reading the documentation for the next release of alexa-app-server, which should be 3.0.2. 74 | The current stable release is 3.0.1. 75 | ``` 76 | 77 | Add the next release to [CHANGELOG.md](CHANGELOG.md). 78 | 79 | ``` 80 | #### 3.0.2 (Next) 81 | 82 | * Your contribution here. 83 | ``` 84 | 85 | Bump the minor version in package.json. 86 | 87 | Commit your changes. 88 | 89 | ``` 90 | git add CHANGELOG.md README.md package.json 91 | git commit -m "Preparing for next development iteration, 3.0.2." 92 | git push origin master 93 | ``` -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | ## Upgrading 2 | 3 | ### Upgrading to >= 2.4.0 4 | 5 | This is the first release out of the [alexa-js Github org](https://github.com/alexa-js). 6 | 7 | #### Alexa-app minimum version 8 | 9 | A minimum version 3.0.0 of `alexa-app` is now required. 10 | 11 | See [alexa-app CHANGELOG](https://github.com/alexa-js/alexa-app/blob/master/CHANGELOG.md) and [UPGRADING](https://github.com/alexa-js/alexa-app/blob/master/UPGRADING.md) for more information. 12 | 13 | #### Changes in body-parser middleware 14 | 15 | The `.urlencoded` body-parser has been removed. If your application requires it, `use` it from your application code. 16 | 17 | The `.json` body-parser is only used for Alexa applications when `alexa-app-server` is started with `verify: false`, because `verifier-middleware`, which is now mounted inside `alexa-app` acts as a body-parser as well. 18 | 19 | See [#35](https://github.com/alexa-js/alexa-app-server/issues/35) and [#64](https://github.com/alexa-js/alexa-app-server/pull/64) for more information. -------------------------------------------------------------------------------- /dangerfile.js: -------------------------------------------------------------------------------- 1 | import { danger, warn } from "danger" 2 | 3 | // did you forget to update changelog? 4 | const hasChangelog = danger.git.modified_files.includes("CHANGELOG.md"); 5 | if (!hasChangelog) { 6 | warn("Did you forget to update CHANGELOG.md?"); 7 | } 8 | -------------------------------------------------------------------------------- /examples/apps/hello_world/index.js: -------------------------------------------------------------------------------- 1 | var alexa = require('alexa-app'); 2 | 3 | // Allow this module to be reloaded by hotswap when changed 4 | module.change_code = 1; 5 | 6 | // Define an alexa-app 7 | var app = new alexa.app('hello_world'); 8 | app.id = require('./package.json').alexa.applicationId; 9 | 10 | app.launch(function(req, res) { 11 | res.say("Hello World!!"); 12 | }); 13 | 14 | app.intent('NameIntent', { 15 | "slots": { "NAME": "LITERAL", "AGE": "NUMBER" }, 16 | "utterances": ["{My name is|my name's} {matt|bob|bill|jake|nancy|mary|jane|NAME} and I am {1-100|AGE}{ years old|}"] 17 | }, function(req, res) { 18 | res.say('Your name is ' + req.slot('NAME') + ' and you are ' + req.slot('AGE') + ' years old'); 19 | }); 20 | 21 | app.intent('AgeIntent', { 22 | "slots": { "AGE": "NUMBER" }, 23 | "utterances": ["My age is {1-100|AGE}"] 24 | }, function(req, res) { 25 | res.say('Your age is ' + req.slot('AGE')); 26 | }); 27 | 28 | module.exports = app; 29 | -------------------------------------------------------------------------------- /examples/apps/hello_world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello_world", 3 | "version": "1.0.0", 4 | "description": "A sample Alexa app", 5 | "main": "index.js", 6 | "author": "Matt Kruse (http://mattkruse.com/)", 7 | "license": "ISC", 8 | "alexa": { 9 | "applicationId": "amzn1.echo-sdk-ams.app.999999-d0ed-9999-ad00-999999d00ebe" 10 | }, 11 | "dependencies": { 12 | "alexa-app": "^2.1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/apps/number_guessing_game/index.js: -------------------------------------------------------------------------------- 1 | var alexa = require('alexa-app'); 2 | 3 | // Allow this module to be reloaded by hotswap when changed 4 | module.change_code = 1; 5 | 6 | // Define an alexa-app 7 | var app = new alexa.app('number_guessing_game'); 8 | app.id = require('./package.json').alexa.applicationId; 9 | 10 | app.launch(function(req, res) { 11 | var number = Math.floor(Math.random() * 99) + 1; 12 | res.session('number', number); 13 | res.session('guesses', 0); 14 | var prompt = "Guess a number between 1 and 100!"; 15 | res.say(prompt).reprompt(prompt).shouldEndSession(false); 16 | }); 17 | 18 | app.intent('GuessIntent', { 19 | "slots": { "guess": "NUMBER" }, 20 | "utterances": ["{1-100|guess}"] 21 | }, 22 | function(req, res) { 23 | var guesses = (+req.session('guesses')) + 1; 24 | var guess = req.slot('guess'); 25 | var number = +req.session('number'); 26 | if (!guess) { 27 | res.say("Sorry, I didn't hear a number. The number was " + number); 28 | } else if (guess == number) { 29 | res.say("Congratulations, you guessed the number in " + guesses + (guesses == 1 ? " try" : " tries")); 30 | } else { 31 | if (guess > number) { 32 | res.say("Guess lower"); 33 | } else if (guess < number) { 34 | res.say("Guess higher"); 35 | } 36 | res.reprompt("Sorry, I didn't hear a number. Try again."); 37 | res.session('guesses', guesses); 38 | res.shouldEndSession(false); 39 | } 40 | } 41 | ); 42 | 43 | module.exports = app; 44 | -------------------------------------------------------------------------------- /examples/apps/number_guessing_game/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "number_guessing_game", 3 | "version": "1.0.0", 4 | "description": "A sample number guessing game for Alexa", 5 | "main": "index.js", 6 | "author": "Matt Kruse (http://mattkruse.com/)", 7 | "license": "ISC", 8 | "alexa": { 9 | "applicationId": "amzn1.echo-sdk-ams.app.999999-d0ed-9999-ad00-999999d00ebe" 10 | }, 11 | "dependencies": { 12 | "alexa-app": "^2.1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/public_html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | alexa-app-server is running 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/server.js: -------------------------------------------------------------------------------- 1 | var AlexaAppServer = require("../index.js"); 2 | 3 | AlexaAppServer.start({ 4 | server_root: __dirname, 5 | port: 8080, 6 | // Use preRequest to load user data on each request and add it to the request json. 7 | // In reality, this data would come from a db or files, etc. 8 | preRequest: function(json, req, res) { 9 | console.log("preRequest fired"); 10 | json.userDetails = { "name": "Bob Smith" }; 11 | }, 12 | // Add a dummy attribute to the response 13 | postRequest: function(json, req, res) { 14 | json.dummy = "text"; 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /examples/server/login.js: -------------------------------------------------------------------------------- 1 | module.exports = function(express,alexaAppServerObject) { 2 | express.use('/login',function(req,res) { 3 | res.send("Imagine this is a dynamic server-side login action"); 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /examples/sslcert/cert.ca_bundle: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFuTCCA6GgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCVVMx 3 | EzARBgNVBAgMCkNhbGlmb3JuaWExETAPBgNVBAoMCEFsZXhhLUpTMSMwIQYJKoZI 4 | hvcNAQkBFhR0ZWphc2hhaDg4QGdtYWlsLmNvbTAeFw0xNzAxMjkwNTU1MjJaFw0y 5 | NzAxMjcwNTU1MjJaMH0xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh 6 | MREwDwYDVQQKDAhBbGV4YS1KUzEhMB8GA1UEAwwYQWxleGEtSlMgSW50ZXJtZWRp 7 | YXRlIENBMSMwIQYJKoZIhvcNAQkBFhR0ZWphc2hhaDg4QGdtYWlsLmNvbTCCAiIw 8 | DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANIUHxiLoXqiCAN8zRHkrQlY5ywJ 9 | KSLfsM2IWeADYDhF7YRbM4EpmCEkwTAAeOx4BlTmqgk3CnQGySvuEE4lg22bvufC 10 | I1q5hFV24Jp1p7UDYhloLwBLIRS1M/k4wwJzoSMJi5YMEWWageZy34+0oJFqVYQS 11 | bLXU5leUfc7/xB+bUepzoavsHBOClQg7efI0O0llovAmeVlUzTIMuFvzNv3uyME2 12 | U29rkxlHnE+FOsFE4CS7Z/mJ77jJBPFB/N/FAEqX8CcT4Er4eaQo1bpEWuCZ+03h 13 | qQnpLhoJI10VbMJn5xKyxPqhAWv5MPdT1ZiboX0eVj7IDQ5/FvPSJA+Q38RFHGoJ 14 | KiiouTi5iSOFEMknfbIGpa4ppOQ26LWnPso8w4ov+RILK/dNsvHy0OFywJ2vgEPz 15 | M1hljNtczZSqLu4wK9A03EXh0sk02oUcLe9lxA1pq1oyq7OBYmB3jSnnR55Yz9YH 16 | OMDLB6iPEArSNiAh0AYIJNDEuZxnEsCHLed8XK4xi46fsaoP5rwemVAYV/iCfeJi 17 | 6/B3PNMZYI0Hh+XwPztpZbMDxiOU7cvcY77RvsARJ3qD+fcBLZj25sSM8qn6Iu7J 18 | 8ORKnXtSzIUQkwRb42N4myQFqzeQZS9z3/X6zyIBstReb8oYbH+32M3NN5VPnRvC 19 | vVtLACEFs17F0ezNAgMBAAGjZjBkMB0GA1UdDgQWBBQPskki+dViw7LUYufUGyaS 20 | MCa5XDAfBgNVHSMEGDAWgBRSlCLzcX/AHm8/V/HC25fgnbDlvDASBgNVHRMBAf8E 21 | CDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAAiPX 22 | cBS8NqRqQkj9KDGWp8BsWBxtbm8UqdS/kJIfKyG3fUxI01KbvJXKCQwYmVI5E5MP 23 | F/n3tYhoSNvRqcx2mkiBG2QTcSEzqXHXOsT9ou46t8755799ECBqK11kiKhKshql 24 | awlcuqvQe8Ymyg2yeICHywamlCjrgMWEU4zWLX6f5I3/RC3KVV5uWIbbqSPG4VBv 25 | EOcwjrzgwdZLyssvhk9yjVudLH0n590B5V1twUPJdI3WBZWwKThhnjjxDBxUTweg 26 | 9iqOgRqpq+Asnv55FFJis+ytt6Wo/K/aB7tFz9w7Dyf0OirhaPm3QiUE2etoOaEe 27 | Us3WIrzjOtI6Bf2I3Nrtvp3JfWUvUnoPeGx/5teP0fPieXjZfBmSXjOHudS5K997 28 | G93aebh0bsWYSSImCc3qeH3HADbo/MwOn+XAvPYUd8Y7gS9FO8oVo1nthiz0b26x 29 | eSX4QkRTa7cRLI/uFkIXmgEzZAL5DopNatv5jVHPJyggHLbvgyqQvjycUjVHVi8C 30 | XL8c79TJE9b0kIfhg56p+qS/bgnem+6Vr7fT3zE4yB8B9uPl8B4ZKFhdrwGOQ2/o 31 | RiAFxkPK+lv+q11v+iFG2JaQctjeY5i/amXs8TNB1EIe1ha9igdEJONmufK8jJyD 32 | L5Mca2d0qwGPMR4x6/LCaSBIXsXX/5IgmM1N0GE= 33 | -----END CERTIFICATE----- 34 | -----BEGIN CERTIFICATE----- 35 | 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 65 | -----END CERTIFICATE----- 66 | -------------------------------------------------------------------------------- /examples/sslcert/cert.cer: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /examples/sslcert/private-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-256-CBC,7D460A2386298C0BF0737BA2F7E3234B 4 | 5 | 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 54 | -----END RSA PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var hotswap = require('hotswap'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var http = require('http'); 5 | var https = require('https'); 6 | var express = require('express'); 7 | var alexa = require('alexa-app'); 8 | var Promise = require('bluebird'); 9 | var defaults = require("lodash.defaults"); 10 | var utils = require("./utils"); 11 | 12 | var appServer = function(config) { 13 | var self = {}; 14 | config = config || {}; 15 | 16 | var defaultOptions = { 17 | log: true, 18 | debug: true, 19 | verify: false, 20 | port: process.env.port || 8080, 21 | httpEnabled: true, 22 | httpsEnabled: false, 23 | public_html: 'public_html', 24 | server_dir: 'server', 25 | server_root: __dirname, 26 | app_root: 'alexa', 27 | app_dir: 'apps' 28 | }; 29 | 30 | self.config = defaults(config, defaultOptions); 31 | 32 | if (self.config.verify && self.config.debug) { 33 | throw new Error("invalid configuration: the verify and debug options cannot be both enabled"); 34 | } 35 | 36 | if (!self.config.httpEnabled && !self.config.httpsEnabled) { 37 | throw new Error("invalid configuration: either http or https must be enabled"); 38 | } 39 | 40 | if (self.config.httpEnabled && self.config.httpsEnabled && self.config.port == self.config.httpsPort) { 41 | throw new Error("invalid configuration: http and https ports must be different"); 42 | } 43 | 44 | self.apps = {}; 45 | 46 | self.log = function(msg) { 47 | if (self.config.log) { 48 | console.log(msg); 49 | } 50 | }; 51 | 52 | self.error = function(msg) { 53 | console.error(msg); 54 | }; 55 | 56 | // configure hotswap to watch for changes and swap out module code 57 | var hotswapCallback = function(filename) { 58 | self.log("hotswap reloaded " + filename); 59 | }; 60 | 61 | var errorCallback = function(e) { 62 | self.error("-----\nhotswap error: " + e + "\n-----\n"); 63 | }; 64 | 65 | hotswap.on('swap', hotswapCallback); 66 | hotswap.on('error', errorCallback); 67 | 68 | // load application modules 69 | self.load_apps = function(app_dir, root) { 70 | // set up a router to hang all alexa apps off of 71 | var alexaRouter = express.Router(); 72 | 73 | var normalizedRoot = utils.normalizeApiPath(root); 74 | self.express.use(normalizedRoot, alexaRouter); 75 | 76 | var app_directories = function(srcpath) { 77 | return fs.readdirSync(srcpath).filter(function(file) { 78 | return utils.isValidDirectory(path.join(srcpath, file)); 79 | }); 80 | }; 81 | 82 | app_directories(app_dir).forEach(function(dir) { 83 | var package_json = path.join(app_dir, dir, "package.json"); 84 | if (!utils.isValidFile(package_json)) { 85 | self.error(" package.json not found in directory " + dir); 86 | return; 87 | } 88 | 89 | var pkg = utils.readJsonFile(package_json); 90 | if (!pkg || !pkg.main || !pkg.name) { 91 | self.error(" failed to load " + package_json); 92 | return; 93 | } 94 | 95 | var main = fs.realpathSync(path.join(app_dir, dir, pkg.main)); 96 | if (!utils.isValidFile(main)) { 97 | self.error(" main file not found for app [" + pkg.name + "]: " + main); 98 | return; 99 | } 100 | 101 | var app; 102 | try { 103 | app = require(main); 104 | } catch (e) { 105 | self.error(" error loading app [" + main + "]: " + e); 106 | return; 107 | } 108 | 109 | self.apps[pkg.name] = pkg; 110 | self.apps[pkg.name].exports = app; 111 | if (typeof app.express != "function") { 112 | self.error(" app [" + pkg.name + "] is not an instance of alexa-app"); 113 | return; 114 | } 115 | 116 | // extract Alexa-specific attributes from package.json, if they exist 117 | if (typeof pkg.alexa == "object") { 118 | app.id = pkg.alexa.applicationId; 119 | } 120 | 121 | // attach the alexa-app instance to the alexa router 122 | app.express({ 123 | expressApp: alexaRouter, 124 | debug: self.config.debug, 125 | checkCert: self.config.verify, 126 | preRequest: self.config.preRequest, 127 | postRequest: self.config.postRequest 128 | }); 129 | 130 | var endpoint = path.posix.join(normalizedRoot, app.name); 131 | self.log(" loaded app [" + pkg.name + "] at endpoint: " + endpoint); 132 | }); 133 | 134 | return self.apps; 135 | }; 136 | 137 | // load server modules, eg. code that processes forms, anything that wants to hook into express 138 | self.load_server_modules = function(server_dir) { 139 | var server_files = function(srcpath) { 140 | return fs.readdirSync(srcpath).filter(function(file) { 141 | return utils.isValidFile(path.join(srcpath, file)); 142 | }); 143 | }; 144 | server_files(server_dir).forEach(function(file) { 145 | file = fs.realpathSync(path.join(server_dir, file)); 146 | self.log(" loaded " + file); 147 | var func = require(file); 148 | if (typeof func == "function") { 149 | func(self.express, self); 150 | } 151 | }); 152 | }; 153 | 154 | // start the server 155 | self.start = function() { 156 | self.express = express(); 157 | 158 | self.express.set('views', path.join(__dirname, 'views')); 159 | self.express.set('view engine', 'ejs'); 160 | self.express.use(express.static(path.join(__dirname, 'views'))); 161 | 162 | if (typeof self.config.pre == "function") { 163 | self.config.pre(self); 164 | } 165 | 166 | // serve static content 167 | var static_dir = path.join(self.config.server_root, self.config.public_html); 168 | if (utils.isValidDirectory(static_dir)) { 169 | self.log("serving static content from: " + static_dir); 170 | self.express.use(express.static(static_dir)); 171 | } else { 172 | self.log("not serving static content because directory [" + static_dir + "] does not exist"); 173 | } 174 | 175 | // find any server-side processing modules and let them hook in 176 | var server_dir = path.join(self.config.server_root, self.config.server_dir); 177 | if (utils.isValidDirectory(server_dir)) { 178 | self.log("loading server-side modules from: " + server_dir); 179 | self.load_server_modules(server_dir); 180 | } else { 181 | self.log("no server modules loaded because directory [" + server_dir + "] does not exist"); 182 | } 183 | 184 | // find and load alexa-app modules 185 | var app_dir = path.join(self.config.server_root, self.config.app_dir); 186 | if (utils.isValidDirectory(app_dir)) { 187 | self.log("loading apps from: " + app_dir); 188 | self.load_apps(app_dir, self.config.app_root); 189 | } else { 190 | self.log("apps not loaded because directory [" + app_dir + "] does not exist"); 191 | } 192 | 193 | if (self.config.httpsEnabled) { 194 | self.log("enabling https"); 195 | 196 | if (self.config.privateKey != undefined && self.config.certificate != undefined && self.config.httpsPort != undefined) { 197 | var sslCertRoot = path.join(self.config.server_root, 'sslcert'); 198 | var privateKeyFile = path.join(sslCertRoot, self.config.privateKey); 199 | var certificateFile = path.join(sslCertRoot, self.config.certificate); 200 | var chainFile = (self.config.chain != undefined) ? path.join(sslCertRoot, self.config.chain) : undefined; 201 | 202 | if (utils.isValidFile(privateKeyFile) && utils.isValidFile(certificateFile)) { 203 | var privateKey = utils.readFile(privateKeyFile); 204 | var certificate = utils.readFile(certificateFile); 205 | 206 | var chain = undefined; 207 | if (chainFile != undefined) { 208 | if (utils.isValidFile(chainFile)) { 209 | chain = utils.readFile(chainFile); 210 | } else { 211 | self.error("chain: '" + self.config.chain + "' does not exist in " + sslCertRoot); 212 | } 213 | } 214 | 215 | if (chain == undefined && chainFile != undefined) { 216 | self.error("failed to load chain from " + sslCertRoot + ", https will not be enabled"); 217 | } else if (privateKey != undefined && certificate != undefined) { 218 | var credentials = { 219 | key: privateKey, 220 | cert: certificate 221 | }; 222 | 223 | if (self.config.passphrase != undefined) { 224 | credentials.passphrase = self.config.passphrase 225 | } 226 | 227 | if (chain != undefined) { 228 | credentials.ca = chain; 229 | self.log("using chain certificate from " + sslCertRoot); 230 | } 231 | 232 | try { 233 | // this can fail it the certs were generated incorrectly 234 | var httpsServer = https.createServer(credentials, self.express); 235 | 236 | if (typeof config.host === 'string') { 237 | self.httpsInstance = httpsServer.listen(self.config.httpsPort, self.config.host); 238 | self.log("listening on https://" + self.config.host + ":" + self.config.httpsPort); 239 | } else { 240 | self.httpsInstance = httpsServer.listen(self.config.httpsPort); 241 | self.log("listening on https port " + self.config.httpsPort); 242 | } 243 | } catch (error) { 244 | self.error("failed to listen via https: " + error); 245 | } 246 | } else { 247 | self.error("failed to load privateKey or certificate from " + sslCertRoot + ", https will not be enabled"); 248 | } 249 | } else { 250 | self.error("privateKey: '" + self.config.privateKey + "' or certificate: '" + self.config.certificate + "' do not exist in " + sslCertRoot + ", https will not be enabled"); 251 | } 252 | } else { 253 | self.error("httpsPort, privateKey or certificate parameter is not set in config, https will not be enabled"); 254 | } 255 | } 256 | 257 | if (self.config.httpEnabled) { 258 | if (typeof config.host === 'string') { 259 | self.instance = self.express.listen(self.config.port, self.config.host); 260 | self.log("listening on http://" + self.config.host + ":" + self.config.port); 261 | } else { 262 | self.instance = self.express.listen(self.config.port); 263 | self.log("listening on http port " + self.config.port); 264 | } 265 | } 266 | 267 | if (typeof self.config.post == "function") { 268 | self.config.post(self); 269 | } 270 | 271 | return this; 272 | }; 273 | 274 | // close all server instances 275 | self.stop = function() { 276 | if (typeof self.instance !== "undefined") { 277 | self.instance.close(); 278 | } 279 | 280 | if (typeof self.httpsInstance !== "undefined") { 281 | self.httpsInstance.close(); 282 | } 283 | 284 | // deactivate all hotswap listener 285 | hotswap.removeListener('swap', hotswapCallback); 286 | hotswap.removeListener('error', errorCallback); 287 | }; 288 | 289 | return self; 290 | }; 291 | 292 | // a shortcut start(config) method to avoid creating an instance if not needed 293 | appServer.start = function(config) { 294 | var appServerInstance = new appServer(config); 295 | appServerInstance.start(); 296 | return appServerInstance; 297 | }; 298 | 299 | module.exports = appServer; 300 | -------------------------------------------------------------------------------- /invalid_examples/apps/bad_app_bad_json/index.js: -------------------------------------------------------------------------------- 1 | var alexa = require('alexa-app'); 2 | 3 | // Allow this module to be reloaded by hotswap when changed 4 | module.change_code = 1; 5 | 6 | // Define an alexa-app 7 | var app = new alexa.app('bad_app_bad_json'); 8 | app.launch(function(req,res) { 9 | res.say("This app should not load!"); 10 | }); 11 | module.exports = app; 12 | -------------------------------------------------------------------------------- /invalid_examples/apps/bad_app_bad_json/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "description": "A sample Alexa app", 4 | "author": "Matt Kruse (http://mattkruse.com/)", 5 | "license": "ISC", 6 | "alexa": { 7 | "applicationId":"amzn1.echo-sdk-ams.app.999999-d0ed-9999-ad00-999999d00ebe" 8 | }, 9 | "dependencies": { 10 | "alexa-app": "^2.1.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /invalid_examples/apps/bad_app_bad_request_json/index.js: -------------------------------------------------------------------------------- 1 | var alexa = require('alexa-app'); 2 | 3 | // Allow this module to be reloaded by hotswap when changed 4 | module.change_code = 1; 5 | 6 | // Define an alexa-app 7 | var app = new alexa.app('bad_app_bad_request'); 8 | app.launch(function(req,res) { 9 | res.say("This app should not load!"); 10 | }); 11 | module.exports = app; 12 | -------------------------------------------------------------------------------- /invalid_examples/apps/bad_app_bad_request_json/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bad_app_bad_request", 3 | "version": "1.0.0", 4 | "description": "A sample Alexa app", 5 | "main": "index.js", 6 | "author": "Matt Kruse (http://mattkruse.com/)", 7 | "license": "ISC", 8 | "alexa": { 9 | "applicationId":"amzn1.echo-sdk-ams.app.999999-d0ed-9999-ad00-999999d00ebe" 10 | }, 11 | "dependencies": { 12 | "alexa-app": "^2.1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /invalid_examples/apps/bad_app_name_mismatch/index.js: -------------------------------------------------------------------------------- 1 | var alexa = require('alexa-app'); 2 | 3 | // Allow this module to be reloaded by hotswap when changed 4 | module.change_code = 1; 5 | 6 | // Define an alexa-app 7 | var app = new alexa.app('bad_app_name_mismatch'); 8 | app.launch(function(req,res) { 9 | res.say("This app should message the app name endpoint, not the package.json name"); 10 | }); 11 | module.exports = app; 12 | -------------------------------------------------------------------------------- /invalid_examples/apps/bad_app_name_mismatch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bad_app_name-mismatch", 3 | "version": "1.0.0", 4 | "description": "A sample Alexa app", 5 | "main": "index.js", 6 | "author": "Matt Kruse (http://mattkruse.com/)", 7 | "license": "ISC", 8 | "alexa": { 9 | "applicationId":"amzn1.echo-sdk-ams.app.999999-d0ed-9999-ad00-999999d00ebe" 10 | }, 11 | "dependencies": { 12 | "alexa-app": "^2.1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /invalid_examples/apps/bad_app_no_json/index.js: -------------------------------------------------------------------------------- 1 | var alexa = require('alexa-app'); 2 | 3 | // Allow this module to be reloaded by hotswap when changed 4 | module.change_code = 1; 5 | 6 | // Define an alexa-app 7 | var app = new alexa.app('bad_app_no_json'); 8 | app.launch(function(req,res) { 9 | res.say("This app should not load!"); 10 | }); 11 | module.exports = app; 12 | -------------------------------------------------------------------------------- /invalid_examples/apps/bad_app_not_alexa_app/index.js: -------------------------------------------------------------------------------- 1 | var alexa = require('alexa-app'); 2 | 3 | // Allow this module to be reloaded by hotswap when changed 4 | module.change_code = 1; 5 | 6 | // Define an alexa-app 7 | var app = new alexa.app('bad_app_not_alexa_app'); 8 | app.launch(function(req,res) { 9 | res.say("This app should not load!"); 10 | }); 11 | module.exports = {}; 12 | -------------------------------------------------------------------------------- /invalid_examples/apps/bad_app_not_alexa_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bad_app_not_alexa_app", 3 | "version": "1.0.0", 4 | "description": "A sample Alexa app", 5 | "main": "index.js", 6 | "author": "Matt Kruse (http://mattkruse.com/)", 7 | "license": "ISC", 8 | "alexa": { 9 | "applicationId":"amzn1.echo-sdk-ams.app.999999-d0ed-9999-ad00-999999d00ebe" 10 | }, 11 | "dependencies": { 12 | "alexa-app": "^2.1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /invalid_examples/apps/hello_world/index.js: -------------------------------------------------------------------------------- 1 | var alexa = require('alexa-app'); 2 | 3 | // Allow this module to be reloaded by hotswap when changed 4 | module.change_code = 1; 5 | 6 | // Define an alexa-app 7 | var app = new alexa.app('hello_world'); 8 | app.launch(function(req, res) { 9 | res.say("Hello World!!"); 10 | }); 11 | 12 | app.intent('NameIntent', { 13 | "slots": { "NAME": "LITERAL", "AGE": "NUMBER" }, 14 | "utterances": ["{My name is|my name's} {matt|bob|bill|jake|nancy|mary|jane|NAME} and I am {1-100|AGE}{ years old|}"] 15 | }, function(req, res) { 16 | res.say('Your name is ' + req.slot('NAME') + ' and you are ' + req.slot('AGE') + ' years old'); 17 | }); 18 | 19 | app.intent('AgeIntent', { 20 | "slots": { "AGE": "NUMBER" }, 21 | "utterances": ["My age is {1-100|AGE}"] 22 | }, function(req, res) { 23 | res.say('Your age is ' + req.slot('AGE')); 24 | }); 25 | 26 | module.exports = app; 27 | -------------------------------------------------------------------------------- /invalid_examples/apps/hello_world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello_world", 3 | "version": "1.0.0", 4 | "description": "A sample Alexa app", 5 | "main": "index.js", 6 | "author": "Matt Kruse (http://mattkruse.com/)", 7 | "license": "ISC", 8 | "alexa": { 9 | "applicationId": "amzn1.echo-sdk-ams.app.999999-d0ed-9999-ad00-999999d00ebe" 10 | }, 11 | "dependencies": { 12 | "alexa-app": "^2.1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /invalid_examples/public_html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | alexa-app-server is running 4 | 5 | 6 | -------------------------------------------------------------------------------- /invalid_examples/server.js: -------------------------------------------------------------------------------- 1 | var AlexaAppServer = require("../index.js"); 2 | AlexaAppServer.start( { 3 | port:8080 4 | // Use preRequest to load user data on each request and add it to the request json. 5 | // In reality, this data would come from a db or files, etc. 6 | ,preRequest: function(json,req,res) { 7 | console.log("preRequest fired"); 8 | json.userDetails = { "name":"Bob Smith" }; 9 | } 10 | // Add a dummy attribute to the response 11 | ,postRequest: function(json,req,res) { 12 | json.dummy = "text"; 13 | } 14 | } ); 15 | -------------------------------------------------------------------------------- /invalid_examples/server/login.js: -------------------------------------------------------------------------------- 1 | module.exports = function(express,alexaAppServerObject) { 2 | express.use('/login',function(req,res) { 3 | res.send("Imagine this is a dynamic server-side login action"); 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /invalid_examples/sslcert/cert.ca_bundle: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFuTCCA6GgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCVVMx 3 | EzARBgNVBAgMCkNhbGlmb3JuaWExETAPBgNVBAoMCEFsZXhhLUpTMSMwIQYJKoZI 4 | hvcNAQkBFhR0ZWphc2hhaDg4QGdtYWlsLmNvbTAeFw0xNzAxMjkwNTU1MjJaFw0y 5 | NzAxMjcwNTU1MjJaMH0xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh 6 | MREwDwYDVQQKDAhBbGV4YS1KUzEhMB8GA1UEAwwYQWxleGEtSlMgSW50ZXJtZWRp 7 | YXRlIENBMSMwIQYJKoZIhvcNAQkBFhR0ZWphc2hhaDg4QGdtYWlsLmNvbTCCAiIw 8 | DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANIUHxiLoXqiCAN8zRHkrQlY5ywJ 9 | KSLfsM2IWeADYDhF7YRbM4EpmCEkwTAAeOx4BlTmqgk3CnQGySvuEE4lg22bvufC 10 | I1q5hFV24Jp1p7UDYhloLwBLIRS1M/k4wwJzoSMJi5YMEWWageZy34+0oJFqVYQS 11 | bLXU5leUfc7/xB+bUepzoavsHBOClQg7efI0O0llovAmeVlUzTIMuFvzNv3uyME2 12 | U29rkxlHnE+FOsFE4CS7Z/mJ77jJBPFB/N/FAEqX8CcT4Er4eaQo1bpEWuCZ+03h 13 | qQnpLhoJI10VbMJn5xKyxPqhAWv5MPdT1ZiboX0eVj7IDQ5/FvPSJA+Q38RFHGoJ 14 | KiiouTi5iSOFEMknfbIGpa4ppOQ26LWnPso8w4ov+RILK/dNsvHy0OFywJ2vgEPz 15 | M1hljNtczZSqLu4wK9A03EXh0sk02oUcLe9lxA1pq1oyq7OBYmB3jSnnR55Yz9YH 16 | OMDLB6iPEArSNiAh0AYIJNDEuZxnEsCHLed8XK4xi46fsaoP5rwemVAYV/iCfeJi 17 | 6/B3PNMZYI0Hh+XwPztpZbMDxiOU7cvcY77RvsARJ3qD+fcBLZj25sSM8qn6Iu7J 18 | 8ORKnXtSzIUQkwRb42N4myQFqzeQZS9z3/X6zyIBstReb8oYbH+32M3NN5VPnRvC 19 | vVtLACEFs17F0ezNAgMBAAGjZjBkMB0GA1UdDgQWBBQPskki+dViw7LUYufUGyaS 20 | MCa5XDAfBgNVHSMEGDAWgBRSlCLzcX/AHm8/V/HC25fgnbDlvDASBgNVHRMBAf8E 21 | CDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAAiPX 22 | cBS8NqRqQkj9KDGWp8BsWBxtbm8UqdS/kJIfKyG3fUxI01KbvJXKCQwYmVI5E5MP 23 | F/n3tYhoSNvRqcx2mkiBG2QTcSEzqXHXOsT9ou46t8755799ECBqK11kiKhKshql 24 | awlcuqvQe8Ymyg2yeICHywamlCjrgMWEU4zWLX6f5I3/RC3KVV5uWIbbqSPG4VBv 25 | EOcwjrzgwdZLyssvhk9yjVudLH0n590B5V1twUPJdI3WBZWwKThhnjjxDBxUTweg 26 | 9iqOgRqpq+Asnv55FFJis+ytt6Wo/K/aB7tFz9w7Dyf0OirhaPm3QiUE2etoOaEe 27 | Us3WIrzjOtI6Bf2I3Nrtvp3JfWUvUnoPeGx/5teP0fPieXjZfBmSXjOHudS5K997 28 | G93aebh0bsWYSSImCc3qeH3HADbo/MwOn+XAvPYUd8Y7gS9FO8oVo1nthiz0b26x 29 | eSX4QkRTa7cRLI/uFkIXmgEzZAL5DopNatv5jVHPJyggHLbvgyqQvjycUjVHVi8C 30 | XL8c79TJE9b0kIfhg56p+qS/bgnem+6Vr7fT3zE4yB8B9uPl8B4ZKFhdrwGOQ2/o 31 | RiAFxkPK+lv+q11v+iFG2JaQctjeY5i/amXs8TNB1EIe1ha9igdEJONmufK8jJyD 32 | L5Mca2d0qwGPMR4x6/LCaSBIXsXX/5IgmM1N0GE= 33 | -----END CERTIFICATE----- 34 | -----BEGIN CERTIFICATE----- 35 | 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 65 | -----END CERTIFICATE----- 66 | -------------------------------------------------------------------------------- /invalid_examples/sslcert/cert.cer: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /invalid_examples/sslcert/private-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-256-CBC,7D460A2386298C0BF0737BA2F7E3234B 4 | 5 | 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 54 | -----END RSA PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alexa-app-server", 3 | "version": "3.0.3", 4 | "description": "A web server module for Alexa (Amazon Echo) apps (skills) using Node.js, Express, and alexa-app.", 5 | "main": "index.js", 6 | "author": "Matt Kruse", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/alexa-js/alexa-app-server.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/alexa-js/alexa-app-server/issues" 13 | }, 14 | "scripts": { 15 | "test": "./node_modules/.bin/mocha", 16 | "coverage": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- -R spec", 17 | "coverage_local": "istanbul cover node_modules/mocha/bin/_mocha", 18 | "danger": "./node_modules/.bin/danger" 19 | }, 20 | "license": "MIT", 21 | "dependencies": { 22 | "alexa-app": "^3.2.0", 23 | "bluebird": "^3.4.7", 24 | "body-parser": "~1.16.0", 25 | "ejs": "~2.5.5", 26 | "express": "~4.14.1", 27 | "hotswap": "^1.1.0", 28 | "lodash.defaults": "^4.2.0" 29 | }, 30 | "devDependencies": { 31 | "chai": "^3.5.0", 32 | "coveralls": "^2.11.15", 33 | "danger": "0.11.4", 34 | "istanbul": "0.4.5", 35 | "mocha": "^3.2.0", 36 | "sinon": "^1.17.7", 37 | "sinon-chai": "^2.8.0", 38 | "supertest": "^3.0.0", 39 | "tcp-port-used": "0.1.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "pangram": "The quick brown fox jumps over the lazy dog" 3 | } -------------------------------------------------------------------------------- /test/example.txt: -------------------------------------------------------------------------------- 1 | The quick brown fox jumps over the lazy dog -------------------------------------------------------------------------------- /test/sample-launch-req.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "session": { 4 | "new": true, 5 | "sessionId": "amzn1.echo-api.session.abeee1a7-aee0-41e6-8192-e6faaed9f5ef", 6 | "application": { 7 | "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" 8 | }, 9 | "attributes": {}, 10 | "user": { 11 | "userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2" 12 | } 13 | }, 14 | "request": { 15 | "type": "LaunchRequest", 16 | "requestId": "amzn1.echo-api.request.9cdaa4db-f20e-4c58-8d01-c75322d6c423", 17 | "timestamp": "2015-05-13T12:34:56Z", 18 | "locale": "en-US" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/sample-schema.txt: -------------------------------------------------------------------------------- 1 | { 2 | "intents": [ 3 | { 4 | "intent": "NameIntent", 5 | "slots": [ 6 | { 7 | "name": "NAME", 8 | "type": "LITERAL" 9 | }, 10 | { 11 | "name": "AGE", 12 | "type": "NUMBER" 13 | } 14 | ] 15 | }, 16 | { 17 | "intent": "AgeIntent", 18 | "slots": [ 19 | { 20 | "name": "AGE", 21 | "type": "NUMBER" 22 | } 23 | ] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /test/sample-utterances.txt: -------------------------------------------------------------------------------- 1 | NameIntent My name is {matt|NAME} and I am {one|AGE} years old 2 | NameIntent my name's {bob|NAME} and I am {two|AGE} years old 3 | NameIntent My name is {bill|NAME} and I am {three|AGE} 4 | NameIntent my name's {jake|NAME} and I am {four|AGE} 5 | NameIntent My name is {nancy|NAME} and I am {five|AGE} years old 6 | NameIntent my name's {mary|NAME} and I am {six|AGE} years old 7 | NameIntent My name is {jane|NAME} and I am {seven|AGE} 8 | NameIntent My name is {matt|NAME} and I am {eight|AGE} years old 9 | NameIntent my name's {bob|NAME} and I am {nine|AGE} years old 10 | NameIntent My name is {bill|NAME} and I am {ten|AGE} 11 | NameIntent my name's {jake|NAME} and I am {eleven|AGE} 12 | NameIntent My name is {nancy|NAME} and I am {twelve|AGE} years old 13 | NameIntent my name's {mary|NAME} and I am {thirteen|AGE} years old 14 | NameIntent My name is {jane|NAME} and I am {fourteen|AGE} 15 | NameIntent My name is {matt|NAME} and I am {fifteen|AGE} years old 16 | NameIntent my name's {bob|NAME} and I am {sixteen|AGE} years old 17 | NameIntent My name is {bill|NAME} and I am {seventeen|AGE} 18 | NameIntent my name's {jake|NAME} and I am {eighteen|AGE} 19 | NameIntent My name is {nancy|NAME} and I am {nineteen|AGE} years old 20 | NameIntent my name's {mary|NAME} and I am {twenty|AGE} years old 21 | NameIntent My name is {jane|NAME} and I am {twenty one|AGE} 22 | NameIntent My name is {matt|NAME} and I am {twenty two|AGE} years old 23 | NameIntent my name's {bob|NAME} and I am {twenty three|AGE} years old 24 | NameIntent My name is {bill|NAME} and I am {twenty four|AGE} 25 | NameIntent my name's {jake|NAME} and I am {twenty five|AGE} 26 | NameIntent My name is {nancy|NAME} and I am {twenty six|AGE} years old 27 | NameIntent my name's {mary|NAME} and I am {twenty seven|AGE} years old 28 | NameIntent My name is {jane|NAME} and I am {twenty eight|AGE} 29 | NameIntent My name is {matt|NAME} and I am {twenty nine|AGE} years old 30 | NameIntent my name's {bob|NAME} and I am {thirty|AGE} years old 31 | NameIntent My name is {bill|NAME} and I am {thirty one|AGE} 32 | NameIntent my name's {jake|NAME} and I am {thirty two|AGE} 33 | NameIntent My name is {nancy|NAME} and I am {thirty three|AGE} years old 34 | NameIntent my name's {mary|NAME} and I am {thirty four|AGE} years old 35 | NameIntent My name is {jane|NAME} and I am {thirty five|AGE} 36 | NameIntent My name is {matt|NAME} and I am {thirty six|AGE} years old 37 | NameIntent my name's {bob|NAME} and I am {thirty seven|AGE} years old 38 | NameIntent My name is {bill|NAME} and I am {thirty eight|AGE} 39 | NameIntent my name's {jake|NAME} and I am {thirty nine|AGE} 40 | NameIntent My name is {nancy|NAME} and I am {forty|AGE} years old 41 | NameIntent my name's {mary|NAME} and I am {forty one|AGE} years old 42 | NameIntent My name is {jane|NAME} and I am {forty two|AGE} 43 | NameIntent My name is {matt|NAME} and I am {forty three|AGE} years old 44 | NameIntent my name's {bob|NAME} and I am {forty four|AGE} years old 45 | NameIntent My name is {bill|NAME} and I am {forty five|AGE} 46 | NameIntent my name's {jake|NAME} and I am {forty six|AGE} 47 | NameIntent My name is {nancy|NAME} and I am {forty seven|AGE} years old 48 | NameIntent my name's {mary|NAME} and I am {forty eight|AGE} years old 49 | NameIntent My name is {jane|NAME} and I am {forty nine|AGE} 50 | NameIntent My name is {matt|NAME} and I am {fifty|AGE} years old 51 | NameIntent my name's {bob|NAME} and I am {fifty one|AGE} years old 52 | NameIntent My name is {bill|NAME} and I am {fifty two|AGE} 53 | NameIntent my name's {jake|NAME} and I am {fifty three|AGE} 54 | NameIntent My name is {nancy|NAME} and I am {fifty four|AGE} years old 55 | NameIntent my name's {mary|NAME} and I am {fifty five|AGE} years old 56 | NameIntent My name is {jane|NAME} and I am {fifty six|AGE} 57 | NameIntent My name is {matt|NAME} and I am {fifty seven|AGE} years old 58 | NameIntent my name's {bob|NAME} and I am {fifty eight|AGE} years old 59 | NameIntent My name is {bill|NAME} and I am {fifty nine|AGE} 60 | NameIntent my name's {jake|NAME} and I am {sixty|AGE} 61 | NameIntent My name is {nancy|NAME} and I am {sixty one|AGE} years old 62 | NameIntent my name's {mary|NAME} and I am {sixty two|AGE} years old 63 | NameIntent My name is {jane|NAME} and I am {sixty three|AGE} 64 | NameIntent My name is {matt|NAME} and I am {sixty four|AGE} years old 65 | NameIntent my name's {bob|NAME} and I am {sixty five|AGE} years old 66 | NameIntent My name is {bill|NAME} and I am {sixty six|AGE} 67 | NameIntent my name's {jake|NAME} and I am {sixty seven|AGE} 68 | NameIntent My name is {nancy|NAME} and I am {sixty eight|AGE} years old 69 | NameIntent my name's {mary|NAME} and I am {sixty nine|AGE} years old 70 | NameIntent My name is {jane|NAME} and I am {seventy|AGE} 71 | NameIntent My name is {matt|NAME} and I am {seventy one|AGE} years old 72 | NameIntent my name's {bob|NAME} and I am {seventy two|AGE} years old 73 | NameIntent My name is {bill|NAME} and I am {seventy three|AGE} 74 | NameIntent my name's {jake|NAME} and I am {seventy four|AGE} 75 | NameIntent My name is {nancy|NAME} and I am {seventy five|AGE} years old 76 | NameIntent my name's {mary|NAME} and I am {seventy six|AGE} years old 77 | NameIntent My name is {jane|NAME} and I am {seventy seven|AGE} 78 | NameIntent My name is {matt|NAME} and I am {seventy eight|AGE} years old 79 | NameIntent my name's {bob|NAME} and I am {seventy nine|AGE} years old 80 | NameIntent My name is {bill|NAME} and I am {eighty|AGE} 81 | NameIntent my name's {jake|NAME} and I am {eighty one|AGE} 82 | NameIntent My name is {nancy|NAME} and I am {eighty two|AGE} years old 83 | NameIntent my name's {mary|NAME} and I am {eighty three|AGE} years old 84 | NameIntent My name is {jane|NAME} and I am {eighty four|AGE} 85 | NameIntent My name is {matt|NAME} and I am {eighty five|AGE} years old 86 | NameIntent my name's {bob|NAME} and I am {eighty six|AGE} years old 87 | NameIntent My name is {bill|NAME} and I am {eighty seven|AGE} 88 | NameIntent my name's {jake|NAME} and I am {eighty eight|AGE} 89 | NameIntent My name is {nancy|NAME} and I am {eighty nine|AGE} years old 90 | NameIntent my name's {mary|NAME} and I am {ninety|AGE} years old 91 | NameIntent My name is {jane|NAME} and I am {ninety one|AGE} 92 | NameIntent My name is {matt|NAME} and I am {ninety two|AGE} years old 93 | NameIntent my name's {bob|NAME} and I am {ninety three|AGE} years old 94 | NameIntent My name is {bill|NAME} and I am {ninety four|AGE} 95 | NameIntent my name's {jake|NAME} and I am {ninety five|AGE} 96 | NameIntent My name is {nancy|NAME} and I am {ninety six|AGE} years old 97 | NameIntent my name's {mary|NAME} and I am {ninety seven|AGE} years old 98 | NameIntent My name is {jane|NAME} and I am {ninety eight|AGE} 99 | NameIntent My name is {matt|NAME} and I am {ninety nine|AGE} years old 100 | NameIntent my name's {bob|NAME} and I am {one hundred|AGE} years old 101 | AgeIntent My age is {one|AGE} 102 | AgeIntent My age is {two|AGE} 103 | AgeIntent My age is {three|AGE} 104 | AgeIntent My age is {four|AGE} 105 | AgeIntent My age is {five|AGE} 106 | AgeIntent My age is {six|AGE} 107 | AgeIntent My age is {seven|AGE} 108 | AgeIntent My age is {eight|AGE} 109 | AgeIntent My age is {nine|AGE} 110 | AgeIntent My age is {ten|AGE} 111 | AgeIntent My age is {eleven|AGE} 112 | AgeIntent My age is {twelve|AGE} 113 | AgeIntent My age is {thirteen|AGE} 114 | AgeIntent My age is {fourteen|AGE} 115 | AgeIntent My age is {fifteen|AGE} 116 | AgeIntent My age is {sixteen|AGE} 117 | AgeIntent My age is {seventeen|AGE} 118 | AgeIntent My age is {eighteen|AGE} 119 | AgeIntent My age is {nineteen|AGE} 120 | AgeIntent My age is {twenty|AGE} 121 | AgeIntent My age is {twenty one|AGE} 122 | AgeIntent My age is {twenty two|AGE} 123 | AgeIntent My age is {twenty three|AGE} 124 | AgeIntent My age is {twenty four|AGE} 125 | AgeIntent My age is {twenty five|AGE} 126 | AgeIntent My age is {twenty six|AGE} 127 | AgeIntent My age is {twenty seven|AGE} 128 | AgeIntent My age is {twenty eight|AGE} 129 | AgeIntent My age is {twenty nine|AGE} 130 | AgeIntent My age is {thirty|AGE} 131 | AgeIntent My age is {thirty one|AGE} 132 | AgeIntent My age is {thirty two|AGE} 133 | AgeIntent My age is {thirty three|AGE} 134 | AgeIntent My age is {thirty four|AGE} 135 | AgeIntent My age is {thirty five|AGE} 136 | AgeIntent My age is {thirty six|AGE} 137 | AgeIntent My age is {thirty seven|AGE} 138 | AgeIntent My age is {thirty eight|AGE} 139 | AgeIntent My age is {thirty nine|AGE} 140 | AgeIntent My age is {forty|AGE} 141 | AgeIntent My age is {forty one|AGE} 142 | AgeIntent My age is {forty two|AGE} 143 | AgeIntent My age is {forty three|AGE} 144 | AgeIntent My age is {forty four|AGE} 145 | AgeIntent My age is {forty five|AGE} 146 | AgeIntent My age is {forty six|AGE} 147 | AgeIntent My age is {forty seven|AGE} 148 | AgeIntent My age is {forty eight|AGE} 149 | AgeIntent My age is {forty nine|AGE} 150 | AgeIntent My age is {fifty|AGE} 151 | AgeIntent My age is {fifty one|AGE} 152 | AgeIntent My age is {fifty two|AGE} 153 | AgeIntent My age is {fifty three|AGE} 154 | AgeIntent My age is {fifty four|AGE} 155 | AgeIntent My age is {fifty five|AGE} 156 | AgeIntent My age is {fifty six|AGE} 157 | AgeIntent My age is {fifty seven|AGE} 158 | AgeIntent My age is {fifty eight|AGE} 159 | AgeIntent My age is {fifty nine|AGE} 160 | AgeIntent My age is {sixty|AGE} 161 | AgeIntent My age is {sixty one|AGE} 162 | AgeIntent My age is {sixty two|AGE} 163 | AgeIntent My age is {sixty three|AGE} 164 | AgeIntent My age is {sixty four|AGE} 165 | AgeIntent My age is {sixty five|AGE} 166 | AgeIntent My age is {sixty six|AGE} 167 | AgeIntent My age is {sixty seven|AGE} 168 | AgeIntent My age is {sixty eight|AGE} 169 | AgeIntent My age is {sixty nine|AGE} 170 | AgeIntent My age is {seventy|AGE} 171 | AgeIntent My age is {seventy one|AGE} 172 | AgeIntent My age is {seventy two|AGE} 173 | AgeIntent My age is {seventy three|AGE} 174 | AgeIntent My age is {seventy four|AGE} 175 | AgeIntent My age is {seventy five|AGE} 176 | AgeIntent My age is {seventy six|AGE} 177 | AgeIntent My age is {seventy seven|AGE} 178 | AgeIntent My age is {seventy eight|AGE} 179 | AgeIntent My age is {seventy nine|AGE} 180 | AgeIntent My age is {eighty|AGE} 181 | AgeIntent My age is {eighty one|AGE} 182 | AgeIntent My age is {eighty two|AGE} 183 | AgeIntent My age is {eighty three|AGE} 184 | AgeIntent My age is {eighty four|AGE} 185 | AgeIntent My age is {eighty five|AGE} 186 | AgeIntent My age is {eighty six|AGE} 187 | AgeIntent My age is {eighty seven|AGE} 188 | AgeIntent My age is {eighty eight|AGE} 189 | AgeIntent My age is {eighty nine|AGE} 190 | AgeIntent My age is {ninety|AGE} 191 | AgeIntent My age is {ninety one|AGE} 192 | AgeIntent My age is {ninety two|AGE} 193 | AgeIntent My age is {ninety three|AGE} 194 | AgeIntent My age is {ninety four|AGE} 195 | AgeIntent My age is {ninety five|AGE} 196 | AgeIntent My age is {ninety six|AGE} 197 | AgeIntent My age is {ninety seven|AGE} 198 | AgeIntent My age is {ninety eight|AGE} 199 | AgeIntent My age is {ninety nine|AGE} 200 | AgeIntent My age is {one hundred|AGE} -------------------------------------------------------------------------------- /test/test-examples-server-app-loading-fail-checks.js: -------------------------------------------------------------------------------- 1 | /*jshint expr: true*/ 2 | "use strict"; 3 | var chai = require("chai"); 4 | var sinon = require("sinon"); 5 | var sinonChai = require("sinon-chai"); 6 | chai.use(sinonChai); 7 | var expect = chai.expect; 8 | chai.config.includeStack = true; 9 | var request = require("supertest"); 10 | var alexaAppServer = require("../index"); 11 | 12 | describe("Alexa App Server with invalid examples", function() { 13 | var testServer; 14 | 15 | afterEach(function() { 16 | testServer.stop(); 17 | }); 18 | 19 | it("starts without loading invalid apps", function() { 20 | testServer = alexaAppServer.start({ 21 | port: 3000, 22 | server_root: 'invalid_examples' 23 | }); 24 | 25 | return request(testServer.express) 26 | .get('/') 27 | .expect(200).then(function(response) { 28 | expect(response.text).to.contain("alexa-app-server is running"); 29 | }); 30 | }); 31 | 32 | it("loads apps with the app name in the endpoint message", function() { 33 | sinon.spy(console, 'log'); 34 | testServer = alexaAppServer.start({ 35 | port: 3000, 36 | server_root: 'invalid_examples' 37 | }); 38 | 39 | var badAppNameMismatch = ' loaded app [bad_app_name-mismatch] at endpoint: /alexa/bad_app_name_mismatch'; 40 | expect(console.log).to.have.been.calledWithExactly(badAppNameMismatch); 41 | 42 | console.log.restore(); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/test-examples-server-custom-bindings.js: -------------------------------------------------------------------------------- 1 | /*jshint expr: true*/ 2 | "use strict"; 3 | var chai = require("chai"); 4 | var expect = chai.expect; 5 | chai.config.includeStack = true; 6 | var request = require("supertest"); 7 | var alexaAppServer = require("../index"); 8 | 9 | describe("Alexa App Server with Examples & Custom Server Bindings", function() { 10 | var testServer; 11 | var default_NODE_TLS_REJECT_UNAUTHORIZED = process.env.NODE_TLS_REJECT_UNAUTHORIZED; 12 | 13 | beforeEach(function() { 14 | // used so that testing https requests will work properly 15 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 16 | }); 17 | 18 | afterEach(function() { 19 | testServer.stop(); 20 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = default_NODE_TLS_REJECT_UNAUTHORIZED; 21 | }); 22 | 23 | it("mounts the hello world app (HTTP only) and bind to the specified address", function() { 24 | testServer = alexaAppServer.start({ 25 | port: 3000, 26 | host: "127.0.0.1", 27 | server_root: 'examples' 28 | }); 29 | 30 | return request("http://127.0.0.1:3000") 31 | .get('/alexa/hello_world') 32 | .expect(200); 33 | }); 34 | 35 | it("mounts the hello world app (HTTP & HTTPS) (CA chain file not included) and bind to the specified address", function() { 36 | testServer = alexaAppServer.start({ 37 | httpsPort: 6000, 38 | host: "127.0.0.1", 39 | server_root: 'examples', 40 | httpsEnabled: true, 41 | privateKey: 'private-key.pem', 42 | certificate: 'cert.cer', 43 | passphrase: 'test123' 44 | }); 45 | 46 | return request("https://127.0.0.1:6000") 47 | .get('/alexa/hello_world') 48 | .expect(200); 49 | }); 50 | 51 | it("mounts the hello world app (HTTP & HTTPS) (CA chain file included) and bind to the specified address", function() { 52 | testServer = alexaAppServer.start({ 53 | httpsPort: 6000, 54 | host: "127.0.0.1", 55 | server_root: 'examples', 56 | httpsEnabled: true, 57 | privateKey: 'private-key.pem', 58 | certificate: 'cert.cer', 59 | chain: 'cert.ca_bundle', 60 | passphrase: 'test123' 61 | }); 62 | 63 | return request("https://127.0.0.1:6000") 64 | .get('/alexa/hello_world') 65 | .expect(200); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/test-examples-server-https-fail-checks.js: -------------------------------------------------------------------------------- 1 | /*jshint expr: true*/ 2 | "use strict"; 3 | var chai = require("chai"); 4 | var expect = chai.expect; 5 | chai.config.includeStack = true; 6 | var request = require("supertest"); 7 | var alexaAppServer = require("../index"); 8 | 9 | describe("Alexa App Server with Examples & HTTPS fail checking", function() { 10 | var testServer; 11 | 12 | afterEach(function() { 13 | testServer.stop(); 14 | }); 15 | 16 | it("fails to mount due to missing HTTPS parameters", function() { 17 | testServer = alexaAppServer.start({ 18 | httpsPort: 3000, 19 | server_root: 'invalid_examples', 20 | httpsEnabled: true 21 | }); 22 | 23 | expect(testServer.httpsInstance).to.not.exist; 24 | }); 25 | 26 | it("fails to mount due to invalid private key and certificate", function() { 27 | testServer = alexaAppServer.start({ 28 | httpsPort: 6000, 29 | server_root: 'invalid_examples', 30 | httpsEnabled: true, 31 | privateKey: 'invalid-private-key.pem', 32 | certificate: 'invalid-cert.cer' 33 | }); 34 | 35 | expect(testServer.httpsInstance).to.not.exist; 36 | }); 37 | 38 | it("fails to mount due to invalid CA chain file", function() { 39 | testServer = alexaAppServer.start({ 40 | httpsPort: 6000, 41 | server_root: 'invalid_examples', 42 | httpsEnabled: true, 43 | privateKey: 'private-key.pem', 44 | certificate: 'cert.cer', 45 | chain: 'invalid-cert.ca_bundle' 46 | }); 47 | 48 | expect(testServer.httpsInstance).to.not.exist; 49 | }); 50 | 51 | it("fails to mount due to no passphrase given", function() { 52 | testServer = alexaAppServer.start({ 53 | httpsPort: 6000, 54 | server_root: 'invalid_examples', 55 | httpsEnabled: true, 56 | privateKey: 'private-key.pem', 57 | certificate: 'cert.cer', 58 | chain: 'cert.ca_bundle' 59 | }); 60 | 61 | expect(testServer.httpsInstance).to.not.exist; 62 | }); 63 | 64 | it("fails to mount due to invalid passphrase", function() { 65 | testServer = alexaAppServer.start({ 66 | httpsPort: 6000, 67 | server_root: 'invalid_examples', 68 | httpsEnabled: true, 69 | privateKey: 'private-key.pem', 70 | certificate: 'cert.cer', 71 | chain: 'cert.ca_bundle', 72 | passphrase: "test321" 73 | }); 74 | 75 | expect(testServer.httpsInstance).to.not.exist; 76 | }); 77 | 78 | // binding to port -1 seems to behave differently on versions of node < 6 79 | xit("fails to mount due to invalid port", function() { 80 | testServer = alexaAppServer.start({ 81 | httpsPort: -1, 82 | server_root: 'invalid_examples', 83 | httpsEnabled: true, 84 | privateKey: 'private-key.pem', 85 | certificate: 'cert.cer', 86 | passphrase: "test123" 87 | }); 88 | 89 | expect(testServer.httpsInstance).to.not.exist; 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/test-examples-server-https-support-extended.js: -------------------------------------------------------------------------------- 1 | /*jshint expr: true*/ 2 | "use strict"; 3 | var chai = require("chai"); 4 | var expect = chai.expect; 5 | chai.config.includeStack = true; 6 | var request = require("supertest"); 7 | var alexaAppServer = require("../index"); 8 | var fs = require("fs"); 9 | var tcpPortUsed = require('tcp-port-used'); 10 | var utils = require("../utils"); 11 | 12 | describe("Alexa App Server with Examples & more HTTPS support", function() { 13 | var testServer; 14 | var sampleLaunchReq; 15 | 16 | before(function() { 17 | sampleLaunchReq = utils.readJsonFile("test/sample-launch-req.json"); 18 | }); 19 | 20 | afterEach(function() { 21 | testServer.stop(); 22 | }); 23 | 24 | describe("no specific address given", function() { 25 | it("has the HTTP server instance running on default port 8080", function() { 26 | testServer = alexaAppServer.start({ 27 | server_root: 'examples' 28 | }); 29 | 30 | return request(testServer.express) 31 | .get('/alexa/hello_world') 32 | .expect(200).then(function(response) { 33 | expect(testServer.instance).to.exist; 34 | return tcpPortUsed.check(8080).then(function(inUse) { 35 | expect(inUse).to.equal(true); 36 | }); 37 | }); 38 | }); 39 | 40 | it("has the HTTP server instance running on configured port 3000", function() { 41 | testServer = alexaAppServer.start({ 42 | port: 3000, 43 | server_root: 'examples' 44 | }); 45 | 46 | return request(testServer.express) 47 | .get('/alexa/hello_world') 48 | .expect(200).then(function(response) { 49 | expect(testServer.instance).to.exist; 50 | return tcpPortUsed.check(3000).then(function(inUse) { 51 | expect(inUse).to.equal(true); 52 | }); 53 | }); 54 | }); 55 | 56 | it("has the HTTPS server instances running", function() { 57 | testServer = alexaAppServer.start({ 58 | httpsPort: 6000, 59 | server_root: 'examples', 60 | httpsEnabled: true, 61 | privateKey: 'private-key.pem', 62 | certificate: 'cert.cer', 63 | chain: 'cert.ca_bundle', 64 | passphrase: "test123" 65 | }); 66 | 67 | return request(testServer.express) 68 | .get('/alexa/hello_world') 69 | .expect(200).then(function(response) { 70 | expect(testServer.instance).to.exist; 71 | expect(testServer.httpsInstance).to.exist; 72 | return tcpPortUsed.check(6000).then(function(inUse) { 73 | expect(inUse).to.equal(true); 74 | }); 75 | }); 76 | }); 77 | }); 78 | 79 | describe("on 127.0.0.1", function() { 80 | it("has the HTTP server instance running", function() { 81 | testServer = alexaAppServer.start({ 82 | port: 3000, 83 | host: '127.0.0.1', 84 | server_root: 'examples' 85 | }); 86 | 87 | return request(testServer.express) 88 | .get('/alexa/hello_world') 89 | .expect(200).then(function(response) { 90 | expect(testServer.instance).to.exist; 91 | expect(testServer.httpsInstance).to.not.exist; 92 | return tcpPortUsed.check(3000).then(function(inUse) { 93 | expect(inUse).to.equal(true); 94 | }); 95 | }); 96 | }); 97 | 98 | it("has both an HTTP and HTTPS server instances running", function() { 99 | testServer = alexaAppServer.start({ 100 | port: 3000, 101 | httpsPort: 6000, 102 | host: '127.0.0.1', 103 | server_root: 'examples', 104 | httpsEnabled: true, 105 | privateKey: 'private-key.pem', 106 | certificate: 'cert.cer', 107 | chain: 'cert.ca_bundle', 108 | passphrase: "test123" 109 | }); 110 | 111 | return request(testServer.express) 112 | .get('/alexa/hello_world') 113 | .expect(200).then(function(response) { 114 | expect(testServer.instance).to.exist; 115 | expect(testServer.httpsInstance).to.exist; 116 | return tcpPortUsed.check(6000).then(function(inUse) { 117 | expect(inUse).to.equal(true); 118 | return tcpPortUsed.check(3000).then(function(inUse) { 119 | expect(inUse).to.equal(true); 120 | }); 121 | }); 122 | }); 123 | }); 124 | 125 | it("with httpEnabled = false only starts an HTTPs instance", function() { 126 | testServer = alexaAppServer.start({ 127 | httpEnabled: false, 128 | port: 3000, 129 | httpsPort: 6000, 130 | host: '127.0.0.1', 131 | server_root: 'examples', 132 | httpsEnabled: true, 133 | privateKey: 'private-key.pem', 134 | certificate: 'cert.cer', 135 | chain: 'cert.ca_bundle', 136 | passphrase: "test123" 137 | }); 138 | 139 | return request(testServer.express) 140 | .get('/alexa/hello_world') 141 | .expect(200).then(function(response) { 142 | expect(testServer.instance).to.not.exist; 143 | expect(testServer.httpsInstance).to.exist; 144 | return tcpPortUsed.check(6000).then(function(inUse) { 145 | expect(inUse).to.equal(true); 146 | return tcpPortUsed.check(3000).then(function(inUse) { 147 | expect(inUse).to.equal(false); 148 | }); 149 | }); 150 | }); 151 | }); 152 | }); 153 | }); 154 | -------------------------------------------------------------------------------- /test/test-examples-server-https-support.js: -------------------------------------------------------------------------------- 1 | /*jshint expr: true*/ 2 | "use strict"; 3 | var chai = require("chai"); 4 | var expect = chai.expect; 5 | chai.config.includeStack = true; 6 | var request = require("supertest"); 7 | var alexaAppServer = require("../index"); 8 | var fs = require("fs"); 9 | var utils = require("../utils"); 10 | 11 | describe("Alexa App Server with Examples & HTTPS support", function() { 12 | var testServer; 13 | var sampleLaunchReq; 14 | 15 | before(function() { 16 | testServer = alexaAppServer.start({ 17 | port: 3000, 18 | server_root: 'examples', 19 | httpsEnabled: true, 20 | httpsPort: 6000, 21 | privateKey: 'private-key.pem', 22 | certificate: 'cert.cer', 23 | chain: 'cert.ca_bundle', 24 | passphrase: "test123" 25 | }); 26 | 27 | sampleLaunchReq = utils.readJsonFile("test/sample-launch-req.json"); 28 | }); 29 | 30 | after(function() { 31 | testServer.stop(); 32 | }); 33 | 34 | describe("GET requests", function() { 35 | it("mounts hello world app", function() { 36 | return request(testServer.express) 37 | .get('/alexa/hello_world') 38 | .expect(200); 39 | }); 40 | 41 | it("mounts number_guessing_game", function() { 42 | return request(testServer.express) 43 | .get('/alexa/number_guessing_game') 44 | .expect(200); 45 | }); 46 | 47 | it("404s on an invalid app", function() { 48 | return request(testServer.express) 49 | .get('/alexa/invalid') 50 | .expect(404); 51 | }); 52 | }); 53 | 54 | describe("POST requests", function() { 55 | it("mounts hello world app", function() { 56 | return request(testServer.express) 57 | .post('/alexa/hello_world') 58 | .send(sampleLaunchReq) 59 | .expect(200); 60 | }); 61 | 62 | it("mounts number_guessing_game", function() { 63 | return request(testServer.express) 64 | .post('/alexa/number_guessing_game') 65 | .send(sampleLaunchReq) 66 | .expect(200); 67 | }); 68 | 69 | it("404s on an invalid app", function() { 70 | return request(testServer.express) 71 | .post('/alexa/invalid') 72 | .send(sampleLaunchReq) 73 | .expect(404); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/test-examples-server-pre-post-functions.js: -------------------------------------------------------------------------------- 1 | /*jshint expr: true*/ 2 | "use strict"; 3 | var chai = require("chai"); 4 | var expect = chai.expect; 5 | chai.config.includeStack = true; 6 | var request = require("supertest"); 7 | var alexaAppServer = require("../index"); 8 | var fs = require("fs"); 9 | var utils = require("../utils"); 10 | 11 | describe("Alexa App Server with Examples & Pre/Post functions", function() { 12 | var testServer, fired; 13 | var sampleLaunchReq; 14 | 15 | before(function() { 16 | fired = {}; 17 | testServer = alexaAppServer.start({ 18 | port: 3000, 19 | server_root: 'examples', 20 | pre: function(appServer) { 21 | fired.pre = true; 22 | }, 23 | post: function(appServer) { 24 | fired.post = true; 25 | }, 26 | preRequest: function(json, request, response) { 27 | fired.preRequest = true; 28 | }, 29 | postRequest: function(json, request, response) { 30 | fired.postRequest = true; 31 | } 32 | }); 33 | 34 | sampleLaunchReq = utils.readJsonFile("test/sample-launch-req.json"); 35 | }); 36 | 37 | after(function() { 38 | testServer.stop(); 39 | }); 40 | 41 | it("mounts hello world app (GET)", function() { 42 | return request(testServer.express) 43 | .get('/alexa/hello_world') 44 | .expect(200).then(function(response) { 45 | expect(fired.pre).to.equal(true); 46 | expect(fired.post).to.equal(true); 47 | // only called for actual Alexa requests 48 | expect(fired.preRequest).to.equal(undefined); 49 | expect(fired.postRequest).to.equal(undefined); 50 | }); 51 | }); 52 | 53 | it("mounts hello world app (POST)", function() { 54 | return request(testServer.express) 55 | .post('/alexa/hello_world') 56 | .send(sampleLaunchReq) 57 | .expect(200).then(function(response) { 58 | expect(fired.pre).to.equal(true); 59 | expect(fired.post).to.equal(true); 60 | expect(fired.preRequest).to.equal(true); 61 | expect(fired.postRequest).to.equal(true); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/test-examples-server-verification.js: -------------------------------------------------------------------------------- 1 | /*jshint expr: true*/ 2 | "use strict"; 3 | var chai = require("chai"); 4 | var expect = chai.expect; 5 | chai.config.includeStack = true; 6 | var request = require("supertest"); 7 | var alexaAppServer = require("../index"); 8 | var fs = require("fs"); 9 | var utils = require("../utils"); 10 | 11 | describe("Alexa App Server with Examples & Verification", function() { 12 | var testServer; 13 | var sampleLaunchReq; 14 | 15 | before(function() { 16 | testServer = alexaAppServer.start({ 17 | port: 3000, 18 | server_root: 'examples', 19 | debug: false, 20 | verify: true 21 | }); 22 | 23 | sampleLaunchReq = utils.readJsonFile("test/sample-launch-req.json"); 24 | }); 25 | 26 | after(function() { 27 | testServer.stop(); 28 | }); 29 | 30 | describe("GET requests", function() { 31 | it("mounts hello world app", function() { 32 | return request(testServer.express) 33 | .get('/alexa/hello_world') 34 | .expect(401); 35 | }); 36 | 37 | it("mounts number_guessing_game app", function() { 38 | return request(testServer.express) 39 | .get('/alexa/number_guessing_game') 40 | .expect(401); 41 | }); 42 | }); 43 | 44 | describe("POST requests", function() { 45 | it("mounts hello world app", function() { 46 | return request(testServer.express) 47 | .post('/alexa/hello_world') 48 | .send(sampleLaunchReq) 49 | .expect(401); 50 | }); 51 | 52 | it("mounts number_guessing_game", function() { 53 | return request(testServer.express) 54 | .post('/alexa/number_guessing_game') 55 | .send(sampleLaunchReq) 56 | .expect(401); 57 | }); 58 | 59 | it("invokes verifier number_guessing_game", function() { 60 | return request(testServer.express) 61 | .post('/alexa/number_guessing_game') 62 | .set('signaturecertchainurl', 'dummy-signature-chain-url') 63 | .set('signature', 'dummy-signature') 64 | .send(sampleLaunchReq) 65 | .expect(401); 66 | }); 67 | 68 | it("invokes verifier number_guessing_game", function() { 69 | return request(testServer.express) 70 | .post('/alexa/number_guessing_game') 71 | .set('signaturecertchainurl', 'dummy-signature-chain-url') 72 | .set('signature', 'dummy-signature') 73 | .send(sampleLaunchReq) 74 | .expect(401); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/test-examples-server.js: -------------------------------------------------------------------------------- 1 | /*jshint expr: true*/ 2 | "use strict"; 3 | var chai = require("chai"); 4 | var expect = chai.expect; 5 | chai.config.includeStack = true; 6 | var request = require("supertest"); 7 | var alexaAppServer = require("../index"); 8 | var fs = require("fs"); 9 | var utils = require("../utils"); 10 | 11 | describe("Alexa App Server with Examples", function() { 12 | var testServer; 13 | var sampleLaunchReq, sampleUtterances, sampleSchema; 14 | 15 | before(function() { 16 | testServer = alexaAppServer.start({ 17 | port: 3000, 18 | server_root: 'examples' 19 | }); 20 | 21 | sampleLaunchReq = utils.readJsonFile("test/sample-launch-req.json"); 22 | sampleUtterances = utils.readFile("test/sample-utterances.txt"); 23 | sampleSchema = utils.readFile("test/sample-schema.txt"); 24 | }); 25 | 26 | after(function() { 27 | testServer.stop(); 28 | }); 29 | 30 | it("starts an express instance", function() { 31 | return request(testServer.express) 32 | .get('/') 33 | .expect(200).then(function(response) { 34 | expect(response.text).to.contain("alexa-app-server is running"); 35 | }); 36 | }); 37 | 38 | describe("GET requests", function() { 39 | it("mounts hello world app", function() { 40 | return request(testServer.express) 41 | .get('/alexa/hello_world') 42 | .expect(200); 43 | }); 44 | 45 | it("mounts number_guessing_game", function() { 46 | return request(testServer.express) 47 | .get('/alexa/number_guessing_game') 48 | .expect(200); 49 | }); 50 | 51 | it("404s on an invalid app", function() { 52 | return request(testServer.express) 53 | .get('/alexa/invalid') 54 | .expect(404); 55 | }); 56 | }); 57 | 58 | describe("POST requests", function() { 59 | it("mounts hello world app", function() { 60 | return request(testServer.express) 61 | .post('/alexa/hello_world') 62 | .send(sampleLaunchReq) 63 | .expect(200); 64 | }); 65 | 66 | it("mounts number_guessing_game", function() { 67 | return request(testServer.express) 68 | .post('/alexa/number_guessing_game') 69 | .send(sampleLaunchReq) 70 | .expect(200); 71 | }); 72 | 73 | it("404s on an invalid app", function() { 74 | return request(testServer.express) 75 | .post('/alexa/invalid') 76 | .send(sampleLaunchReq) 77 | .expect(404); 78 | }); 79 | }); 80 | 81 | describe("schema and utterances", function() { 82 | it("returns the schema of the hello world app", function() { 83 | return request(testServer.express) 84 | .get('/alexa/hello_world?schema') 85 | .expect(200).then(function(res) { 86 | expect(res.text).to.equal.sampleSchema; 87 | }); 88 | }); 89 | 90 | it("returns the utterances of the hello world app", function() { 91 | return request(testServer.express) 92 | .get('/alexa/hello_world?utterances') 93 | .expect(200).then(function(res) { 94 | expect(res.text).to.equal.sampleUtterances; 95 | }); 96 | }); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /test/test-server-config.js: -------------------------------------------------------------------------------- 1 | /*jshint expr: true*/ 2 | "use strict"; 3 | var chai = require("chai"); 4 | var expect = chai.expect; 5 | chai.config.includeStack = true; 6 | var request = require("supertest"); 7 | var alexaAppServer = require("../index"); 8 | 9 | describe("Alexa App Server", function() { 10 | it("throws an error when 'debug' and 'verify' are enabled", function() { 11 | var fn = function() { 12 | alexaAppServer.start({ 13 | debug: true, 14 | verify: true 15 | }); 16 | }; 17 | 18 | expect(fn).to.throw(Error, /verify and debug options cannot be both enabled/); 19 | }); 20 | 21 | it("throws an error when 'httpEnabled' and 'httpsEnabled' are both false", function() { 22 | var fn = function() { 23 | alexaAppServer.start({ 24 | httpEnabled: false, 25 | httpsEnabled: false 26 | }); 27 | }; 28 | 29 | expect(fn).to.throw(Error, /either http or https must be enabled/); 30 | }); 31 | 32 | it("throws an error when 'port' and 'httpsPort' are both the same and http and https are enabled", function() { 33 | var fn = function() { 34 | alexaAppServer.start({ 35 | port: 3000, 36 | httpsPort: 3000, 37 | httpEnabled: true, 38 | httpsEnabled: true 39 | }); 40 | }; 41 | 42 | expect(fn).to.throw(Error, /http and https ports must be different/); 43 | }); 44 | 45 | it("no errors when 'port' and 'httpsPort' are both the same and either is enabled", function() { 46 | var testServer; 47 | var fn = function() { 48 | testServer = alexaAppServer.start({ 49 | port: 3000, 50 | httpsPort: 3000 51 | }); 52 | }; 53 | 54 | expect(fn).to.not.throw(Error); 55 | 56 | testServer.stop(); 57 | }); 58 | 59 | describe("with defaults", function() { 60 | var testServer; 61 | 62 | beforeEach(function() { 63 | testServer = alexaAppServer.start(); 64 | }); 65 | 66 | afterEach(function() { 67 | testServer.stop(); 68 | }); 69 | 70 | it("starts an express instance", function() { 71 | return request(testServer.express) 72 | .get('/') 73 | .expect(404); 74 | }); 75 | 76 | it("defaults host to undefined (binds to any available IP)", function() { 77 | expect(testServer.config.host).to.be.undefined; 78 | }); 79 | 80 | it("defaults port to 8080", function() { 81 | expect(testServer.config.port).to.equal(8080); 82 | }); 83 | 84 | it("has no defaults for httpsPort", function() { 85 | expect(testServer.config.httpsPort).to.be.undefined; 86 | }); 87 | 88 | it("defaults serverRoot to .", function() { 89 | expect(testServer.config.port).to.equal(8080); 90 | }); 91 | }); 92 | 93 | describe("with no host specified", function() { 94 | var testServer; 95 | 96 | beforeEach(function() { 97 | testServer = alexaAppServer.start({ host: '127.0.0.1' }); 98 | }); 99 | 100 | afterEach(function() { 101 | testServer.stop(); 102 | }); 103 | 104 | it("starts an express instance", function() { 105 | return request(testServer.express) 106 | .get('/') 107 | .expect(404); 108 | }); 109 | 110 | it("connects to 127.0.0.1 only", function() { 111 | expect(testServer.config.host).to.equal('127.0.0.1'); 112 | }); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/test-utils.js: -------------------------------------------------------------------------------- 1 | /*jshint expr: true*/ 2 | "use strict"; 3 | var chai = require("chai"); 4 | var expect = chai.expect; 5 | chai.config.includeStack = true; 6 | var utils = require("../utils"); 7 | 8 | describe("Utils", function() { 9 | describe("#isValidFile", function() { 10 | it("verifies that 'README.md' does exists and is a file", function() { 11 | expect(utils.isValidFile('README.md')).to.be.true; 12 | }); 13 | 14 | it("verifies that 'RANDOM.md' does not exists", function() { 15 | expect(utils.isValidFile('RANDOM.md')).to.be.false; 16 | }); 17 | 18 | it("verifies that 'examples' is not a file", function() { 19 | expect(utils.isValidFile('examples')).to.be.false; 20 | }); 21 | }); 22 | 23 | describe("#isValidDirectory", function() { 24 | it("verifies that 'examples' does exists and is a directory", function() { 25 | expect(utils.isValidDirectory('examples')).to.be.true; 26 | }); 27 | 28 | it("verifies that 'random' does not exists", function() { 29 | expect(utils.isValidDirectory('random')).to.be.false; 30 | }); 31 | 32 | it("verifies that 'README.md' is not a directory", function() { 33 | expect(utils.isValidDirectory('README.md')).to.be.false; 34 | }); 35 | }); 36 | 37 | describe("#readFile", function() { 38 | it("successfully reads 'example.txt'", function() { 39 | expect(utils.readFile('test/example.txt')).to.equal("The quick brown fox jumps over the lazy dog"); 40 | }); 41 | 42 | it("throws an error when trying to read 'example.text'", function() { 43 | expect(function() { 44 | utils.readFile('test/example.text'); 45 | }).to.throw(Error); 46 | }); 47 | }); 48 | 49 | describe("#readJsonFile", function() { 50 | it("successfully reads 'example.json'", function() { 51 | expect(utils.readJsonFile('test/example.json')).to.deep.equal({ 52 | "pangram": "The quick brown fox jumps over the lazy dog" 53 | }); 54 | }); 55 | 56 | it("throws an error when trying to read 'example.jason'", function() { 57 | expect(function() { 58 | utils.readJsonFile('test/example.jason'); 59 | }).to.throw(Error); 60 | }); 61 | }); 62 | 63 | describe("#normalizeApiPath", function() { 64 | var tests = [ 65 | { original: "alexa", final: "/alexa" }, 66 | { original: "/alexa", final: "/alexa" }, 67 | { original: "//alexa", final: "/alexa" }, 68 | { original: "///alexa", final: "/alexa" }, 69 | { original: "alexa/", final: "/alexa/" }, 70 | { original: "alexa//", final: "/alexa/" }, 71 | { original: "alexa///", final: "/alexa/" }, 72 | { original: "/alexa/", final: "/alexa/" }, 73 | { original: "//alexa//", final: "/alexa/" }, 74 | { original: "///alexa///", final: "/alexa/" }, 75 | ]; 76 | 77 | tests.forEach(function(test) { 78 | it('correctly normalizes ' + test.original + ' into ' + test.final, function() { 79 | expect(utils.normalizeApiPath(test.original)).to.equal(test.final); 80 | }); 81 | }); 82 | }); 83 | }); -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | var isValidDirectory = function(dir) { 5 | return fs.existsSync(dir) && fs.statSync(dir).isDirectory(); 6 | }; 7 | 8 | var isValidFile = function(file) { 9 | return fs.existsSync(file) && fs.statSync(file).isFile(); 10 | }; 11 | 12 | var readFile = function(file) { 13 | return fs.readFileSync(file, 'utf8'); 14 | }; 15 | 16 | var readJsonFile = function(file) { 17 | return JSON.parse(readFile(file)); 18 | }; 19 | 20 | var normalizeApiPath = function(apiPath) { 21 | return path.posix.normalize(path.posix.join('/', apiPath)); 22 | }; 23 | 24 | module.exports = { 25 | isValidDirectory : isValidDirectory, 26 | isValidFile : isValidFile, 27 | readFile : readFile, 28 | readJsonFile : readJsonFile, 29 | normalizeApiPath : normalizeApiPath 30 | }; -------------------------------------------------------------------------------- /views/templates.json: -------------------------------------------------------------------------------- 1 | templates = { 2 | "launch": { 3 | "version": "1.0", 4 | "session": { 5 | "new": true, 6 | "sessionId": "amzn1.echo-api.session.abeee1a7-aee0-41e6-8192-e6faaed9f5ef", 7 | "application": { 8 | "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" 9 | }, 10 | "attributes": {}, 11 | "user": { 12 | "userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2", 13 | "permissions": { 14 | "consentToken": null 15 | }, 16 | "accessToken": null 17 | } 18 | }, 19 | "context": { 20 | "System": { 21 | "application": { 22 | "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" 23 | }, 24 | "user": { 25 | "userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2", 26 | "permissions": { 27 | "consentToken": null 28 | }, 29 | "accessToken": null 30 | }, 31 | "device": { 32 | "deviceId": null, 33 | "supportedInterfaces": { 34 | "AudioPlayer": {} 35 | } 36 | }, 37 | "apiEndpoint": "https://api.amazonalexa.com/" 38 | }, 39 | "AudioPlayer": { 40 | "offsetInMilliseconds": 0, 41 | "playerActivity": "IDLE" 42 | } 43 | }, 44 | "request": { 45 | "type": "LaunchRequest", 46 | "requestId": "amzn1.echo-api.request.9cdaa4db-f20e-4c58-8d01-c75322d6c423", 47 | "timestamp": "2015-05-13T12:34:56Z", 48 | "locale": "en-US" 49 | } 50 | }, 51 | "intent": { 52 | "version": "1.0", 53 | "session": { 54 | "new": false, 55 | "sessionId": "amzn1.echo-api.session.abeee1a7-aee0-41e6-8192-e6faaed9f5ef", 56 | "application": { 57 | "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" 58 | }, 59 | "attributes": {}, 60 | "user": { 61 | "userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2", 62 | "permissions": { 63 | "consentToken": null 64 | }, 65 | "accessToken": null 66 | } 67 | }, 68 | "context": { 69 | "System": { 70 | "application": { 71 | "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" 72 | }, 73 | "user": { 74 | "userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2", 75 | "permissions": { 76 | "consentToken": null 77 | }, 78 | "accessToken": null 79 | }, 80 | "device": { 81 | "deviceId": null, 82 | "supportedInterfaces": { 83 | "AudioPlayer": {} 84 | } 85 | }, 86 | "apiEndpoint": "https://api.amazonalexa.com/" 87 | }, 88 | "AudioPlayer": { 89 | "offsetInMilliseconds": 0, 90 | "playerActivity": "IDLE" 91 | } 92 | }, 93 | "request": { 94 | "type": "IntentRequest", 95 | "requestId": "amzn1.echo-api.request.6919844a-733e-4e89-893a-fdcb77e2ef0d", 96 | "timestamp": "2015-05-13T12:34:56Z", 97 | "locale": "en-US", 98 | "intent": { 99 | "name": "", 100 | "slots": {} 101 | } 102 | } 103 | }, 104 | "session_end": { 105 | "version": "1.0", 106 | "session": { 107 | "new": false, 108 | "sessionId": "amzn1.echo-api.session.abeee1a7-aee0-41e6-8192-e6faaed9f5ef", 109 | "application": { 110 | "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" 111 | }, 112 | "attributes": {}, 113 | "user": { 114 | "userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2", 115 | "permissions": { 116 | "consentToken": null 117 | }, 118 | "accessToken": null 119 | } 120 | }, 121 | "context": { 122 | "System": { 123 | "application": { 124 | "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" 125 | }, 126 | "user": { 127 | "userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2", 128 | "permissions": { 129 | "consentToken": null 130 | }, 131 | "accessToken": null 132 | }, 133 | "device": { 134 | "deviceId": null, 135 | "supportedInterfaces": { 136 | "AudioPlayer": {} 137 | } 138 | }, 139 | "apiEndpoint": "https://api.amazonalexa.com/" 140 | }, 141 | "AudioPlayer": { 142 | "offsetInMilliseconds": 0, 143 | "playerActivity": "IDLE" 144 | } 145 | }, 146 | "request": { 147 | "type": "SessionEndedRequest", 148 | "requestId": "amzn1.echo-api.request.d8c37cd6-0e1c-458e-8877-5bb4160bf1e1", 149 | "timestamp": "2015-05-13T12:34:56Z", 150 | "locale": "en-US", 151 | "reason": "USER_INITIATED" 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /views/test.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Alexa Tester 5 | 6 | 7 | 8 | 9 | 145 | 146 | 157 | 158 | 159 | 160 | This is a simple testing utility to POST to your endpoint and simulate an Alexa request 161 | 162 |

Request

163 |
164 | Type: 165 | 171 | 172 |
173 | Device ID: 174 | 175 |
176 | 177 |
178 | Access Token: 179 | 180 |
181 | 182 |
183 | Consent Token: 184 | 185 |
186 | 187 |
188 | Intent: 189 | 195 |
196 | 197 |
198 | Locale: 199 | 204 |
205 | 206 |
207 | Slot Values:
208 |
209 |
210 | {{slot.name}} : 211 |
212 |
213 |
214 |
215 | 216 |
217 |
{{request|json}}
218 |
219 |
220 | 221 |
222 | 223 |

Session

224 |
225 |
{{session|json}}
226 |
227 | 228 |

Response

229 |
230 |
{{response|json}}
231 |
232 | 233 |

Schema

234 |
235 |
{{schema|json}}
236 |
237 | 238 |

Utterances

239 |
240 |
<%=utterances%>
241 |
242 | 243 | 244 | --------------------------------------------------------------------------------