Giter VIP home page Giter VIP logo

alexa-app's Introduction

alexa-app

A Node module to simplify the development of Alexa skills (applications.)

NPM Build Status Coverage Status

Stable Release

You're reading the documentation for the next release of alexa-app. Please see CHANGELOG and make sure to read UPGRADING when upgrading from a previous version. The current stable release is 3.0.0.

Introduction

This module parses HTTP JSON requests from the Alexa platform and builds the JSON response that consumed by an Alexa-compatible device, such as the Echo.

It provides a DSL for defining intents, convenience methods to more easily build the response, handle session objects, and add cards.

The intent schema definition and sample utterances are included in your application's definition, making it very simple to generate hundreds (or thousands!) of sample utterances with a few lines.

This module provides a way to host a standalone web service for an Alexa skill. If you're looking for a full-fledged application server or the ability to host multiple skills, check out alexa-app-server.

Features

  • simplified handling of requests and generating responses
  • support for asynchronous handlers
  • easy connection into AWS Lambda or Node.js Express, etc.
  • auto-generation of intent schema and sample utterances
  • support for session data
  • comprehensive test suite

AWS Lambda Example

Amazon skills that use alexa-app have a built-in handler method to handle calls from AWS Lambda. You need to make sure that the Handler is set to index.handler, which is the default value.

var app = new alexa.app("sample");

app.intent("number", {
    "slots": { "number": "NUMBER" },
    "utterances": ["say the number {1-100|number}"]
  },
  function(request, response) {
    var number = request.slot("number");
    response.say("You asked for the number " + number);
  }
);

// connect the alexa-app to AWS Lambda
exports.handler = app.lambda();

For backwards compatibility, or if you wish to change the Handler mapping to something other than index.handler, you can use the lambda() function.

A full lambda example is available here.

Express Example

var express = require("express");
var alexa = require("alexa-app");
var express_app = express();

var app = new alexa.app("sample");

app.intent("number", {
    "slots": { "number": "NUMBER" },
    "utterances": ["say the number {1-100|number}"]
  },
  function(request, response) {
    var number = request.slot("number");
    response.say("You asked for the number " + number);
  }
);

// setup the alexa app and attach it to express before anything else
app.express({ expressApp: express_app, router: express.Router() });

// now POST calls to /sample in express will be handled by the app.request() function
// GET calls will not be handled

// from here on, you can setup any other express routes or middleware as normal

The express function accepts the following parameters.

  • expressApp the express instance to attach to
  • router router instance to attach to the express app
  • endpoint the path to attach the router to (e.g., passing 'mine' attaches to /mine)
  • checkCert when true, applies Alexa certificate checking (default: true)
  • debug when true, sets up the route to handle GET requests (default: false)
  • preRequest function to execute before every POST
  • postRequest function to execute after every POST

A full express example is available here.

API

Skills define handlers for launch, intent, and session end, just like normal Alexa development. The alexa-app module provides a layer around this functionality that simplifies the interaction. Each handler gets passed a request and response object, which are custom for this module.

request

// return the type of request received (LaunchRequest, IntentRequest, SessionEndedRequest)
String request.type()

// return the value passed in for a given slot name
String request.slot("slotName")

// check if you can use session (read or write)
Boolean request.hasSession()

// return the session object
Session request.getSession()

// return the request context
request.context

// the raw request JSON object
request.data

response

The response JSON object is automatically built for you. All you need to do is tell it what you want to output.

// tell Alexa to say something; multiple calls to say() will be appended to each other
// all text output is treated as SSML
response.say(String phrase)

// empty the response text
response.clear()

// tell Alexa to re-prompt the user for a response, if it didn't hear anything valid
response.reprompt(String phrase)

// return a card to the user's Alexa app
// for Object definition @see https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interface-reference#card-object
// skill supports card(String title, String content) for backwards compat of type "Simple"
response.card(Object card)

// return a card instructing the user how to link their account to the skill
// this internally sets the card response
response.linkAccount()

// play audio stream (send AudioPlayer.Play directive) @see https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/custom-audioplayer-interface-reference#play-directive
// skill supports stream(String url, String token, String expectedPreviousToken, Integer offsetInMilliseconds)
response.audioPlayerPlayStream(String playBehavior, Object stream)

// stop playing audio stream (send AudioPlayer.Stop directive)
response.audioPlayerStop()

// clear audio player queue (send AudioPlayer.ClearQueue directive)
// clearBehavior is "CLEAR_ALL" by default
response.audioPlayerClearQueue([ String clearBehavior ])

// tell Alexa whether the user's session is over; sessions end by default
// you can optionally pass a reprompt message
response.shouldEndSession(boolean end [, String reprompt] )

// send the response to the Alexa device (success)
// this is not required for synchronous handlers
// you must call this from asynchronous handlers
response.send()

// trigger a response failure
// the internal promise containing the response will be rejected, and should be handled by the calling environment
// instead of the Alexa response being returned, the failure message will be passed
response.fail(String message)

// calls to response can be chained together
response.say("OK").send()

session

// check if you can use session (read or write)
Boolean request.hasSession()

// get the session object
var session = request.getSession()

// set a session variable
// by defailt, Alexa only persists session variables to the next request
// the alexa-app module makes session variables persist across multiple requests
// Note that you *must* use `.set` or `.clear` to update
// session properties. Updating properties of `attributeValue`
// that are objects will not persist until `.set` is called
session.set(String attributeName, String attributeValue)

// return the value of a session variable
String session.get(String attributeName)

// session details, as passed by Amazon in the request
session.details = { ... }

Request Handlers

Your app can define a single handler for the Launch event and the SessionEnded event, and multiple intent handlers.

LaunchRequest

app.launch(function(request, response) {
  response.say("Hello World");
  response.card("Hello World", "This is an example card");
});

IntentRequest

Define the handler for multiple intents using multiple calls to intent(). Intent schema and sample utterances can also be passed to intent(), which is detailed below. Intent handlers that don't return an immediate response (because they do some asynchronous operation) must return false. See example further below.

app.intent("live", {
    "slots": {
      "city": "AMAZON.US_CITY"
    },
    "utterances": [
      "in {-|city}"
    ]
  }, function(request, response) {
    response.say("You live in " + request.slot("city"));
  }
);

app.intent("vacation", function(request, response) {
  response.say("You're now on vacation.");
});

SessionEndRequest

app.sessionEnded(function(request, response) {
  // cleanup the user's server-side session
  logout(request.userId);
  // no response required
});

AudioPlayer Event Request

Define the handler for multiple events using multiple calls to audioPlayer(). You can define only one handler per event. Event handlers that don't return an immediate response (because they do some asynchronous operation) must return false.

You can define handlers for the following events:

  • PlaybackStarted
  • PlaybackFinished
  • PlaybackStopped
  • PlaybackNearlyFinished
  • PlaybackFailed

Read more about AudioPlayer request types in AudioPlayer Interface Doc.

The following example will return play directive with a next audio on AudioPlayer.PlaybackNearlyFinished request.

app.audioPlayer("PlaybackNearlyFinished", function(request, response) {
  // immediate response
  var stream = {
    "url": "https://next-song-url",
    "token": "some_token",
    "expectedPreviousToken": "some_previous_token",
    "offsetInMilliseconds": 0
  };
  response.audioPlayerPlayStream("ENQUEUE", stream);
});

See an example of asynchronous response below.

app.audioPlayer("PlaybackFinished", function(request, response) {
  // async response
  getNextSongFromDB(function(url, token) {
    var stream = {
      "url": url,
      "token": token,
      "expectedPreviousToken": "some_previous_token",
      "offsetInMilliseconds": 0
    };
    response.audioPlayerPlayStream("ENQUEUE", stream);
    response.send();
  });
  return false;
});

Execute Code On Every Request

In addition to specific event handlers, you can define functions that will run on every request.

pre()

Executed before any event handlers. This is useful to setup new sessions, validate the applicationId, or do any other kind of validations.

app.pre = function(request, response, type) {
  if (request.applicationId != "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe") {
    // fail ungracefully
    response.fail("Invalid applicationId");
  }
};

Note that the post() method still gets called, even if the pre() function calls send() or fail(). The post method can always override anything done before it.

post()

The last thing executed for every request. It is even called if there is an exception or if a response has already been sent. The post() function can change anything about the response. It can even turn a response.fail() into a respond.send() with entirely new content. If post() is called after an exception is thrown, the exception itself will be the 4th argument.

app.post = function(request, response, type, exception) {
  if (exception) {
    // always turn an exception into a successful response
    response.clear().say("An error occured: " + exception).send();
  }
};

Schema and Utterances

The alexa-app module makes it easy to define your intent schema and generate many sample utterances. Optionally pass your schema definition along with your intent handler, and extract the generated content using the schema() and utterances() functions on your app.

Schema Syntax

Pass an object with two properties: slots and utterances.

app.intent("sampleIntent", {
    "slots": {
      "NAME": "AMAZON.US_FIRST_NAME",
      "AGE": "AMAZON.NUMBER"
    },
    "utterances": [
      "my {name is|name's} {NAME} and {I am|I'm} {1-100|AGE}{ years old|}"
    ]
  },
  function(request, response) { ... }
);

slots

The slots object is a simple name: type mapping. The type must be one of Amazon's built-in slot types, such as AMAZON.DATE or AMAZON.NUMBER.

custom slot types

[Custom slot types](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interaction-model-reference#Custom Slot Type Syntax) are supported via the following syntax.

app.intent("sampleIntent", {
    "slots": {
      "CustomSlotName": "CustomSlotType"
    },
    "utterances": [
      "airport {information|status} for {-|CustomSlotName}"
    ]
  },
  function(request, response) { ... }
);

This will result in the following utterance list.

sampleIntent     airport information for {CustomSlotName}
sampleIntent     airport status for {CustomSlotName}

Note that the "CustomSlotType" type values must be specified in the Skill Interface's Interaction Model for the custom slot type to function correctly.

utterances

The utterances syntax allows you to generate many (hundreds or even thousands) of sample utterances using just a few samples that get auto-expanded. Any number of sample utterances may be passed in the utterances array.

This module internally uses alexa-utterances to expand these convenient strings into a format that alexa understands. Read the documentation there for a thorough set of examples on how to use this.

Using a Dictionary

Several intents may use the same list of possible values, so you want to define them in one place, not in each intent schema. Use the app's dictionary.

app.dictionary = {"colors":["red","green","blue"]};
...
"my favorite color is {colors|FAVEORITE_COLOR}"
"I like {colors|COLOR}"

Generating Schema and Utterances Output

To get the generated content out of your app, call the schema() and utterances() functions. See example/express.js for one way to output this data.

// returns a String representation of the JSON object
app.schema() =>

{
  "intents": [{
    "intent": "MyColorIsIntent",
    "slots": [{
      "name": "Color",
      "type": "AMAZON.Color"
    }]
  }]
}

app.utterances() =>

MyColorIsIntent  my color is {dark brown|Color}
MyColorIsIntent  my color is {green|Color}
MyColorIsIntent  my favorite color is {red|Color}
MyColorIsIntent  my favorite color is {navy blue|Color}
WhatsMyColorIntent whats my color
WhatsMyColorIntent what is my color
WhatsMyColorIntent say my color
WhatsMyColorIntent tell me my color
WhatsMyColorIntent whats my favorite color
WhatsMyColorIntent what is my favorite color
WhatsMyColorIntent say my favorite color
WhatsMyColorIntent tell me my favorite color
WhatsMyColorIntent tell me what my favorite color is

Cards

The response.card(Object card) method allows you to send Home Cards on the Alexa app, the companion app available for Fire OS, Android, iOS, and desktop web browsers.

The full specification for the card object passed to this method can be found here.

Card's do not support SSML

If you just want to display a card that presents the user to link their account call response.linkAccount() as a shortcut.

Card Examples

Display text only, aka Simple.

response.card({
  type: "Simple",
  title: "My Cool Card", // this is not required for type Simple
  content: "This is the\ncontent of my card"
});

Display text and image, aka Standard.

Make sure to read the restrictions on hosting the images. Must support CORS AND SSL cert signed by an Amazon approved certification authority.

response.card({
  type: "Standard",
  title: "My Cool Card", // this is not required for type Simple or Standard
  text: "Your ride is on the way to 123 Main Street!\nEstimated cost for this ride: $25",
  image: { // image is optional
    smallImageUrl: "https://carfu.com/resources/card-images/race-car-small.png", // required
    largeImageUrl: "https://carfu.com/resources/card-images/race-car-large.png"
  }
});

Error Handling

Handler functions should not throw exceptions. Ideally, you should catch errors in your handlers using try/catch and respond with an appropriate output to the user. If exceptions do leak out of handlers, they will be thrown by default. Any exceptions can be handled by a generic error handler which you can define for your app. Error handlers cannot be asynchronous.

app.error = function(exception, request, response) {
  response.say("Sorry, something bad happened");
};

If you do want exceptions to bubble out to the caller (and potentially cause Express to crash, for example), you can throw the exception.

app.error = function(exception, request, response) {
  console.log(exception);
  throw exception;
};

Asynchronous Intent Handler Example

If an intent or other request handler will return a response later, it must return ether false or a Promise (object with a .then function). This tells the alexa-app library not to send the response automatically.

A callback is also passed to the handler. When this callback is called with no first argument, the response will be sent. If something is passed to the first argument, it is treated as an error.

If you return a Promise from the handler, you do not need to call the callback. If the Promise resolves, the response will be sent. If it is rejected, it is treated as an error.

app.intent("checkStatus", function(request, response, callback) {
  http.get("http://server.com/status.html", function(rc) {
    // this is async and will run after the http call returns
    // you can send an error to the callback
    if (rc.statusText >= 400) {
        return callback(new Error("Bad request"));
    }

    response.say(rc.statusText);
    // call the callback to send the response
    callback();
  });
  // return false immediately so alexa-app doesn't send the response
  return false;
});

app.intent("checkStatus", function(request, response) {
  // `getAsync` returns a Promise in this example. When
  // returning a Promise, the response is sent after it
  // resolves. If rejected, it is treated as an error.
  return http.getAsync("http://server.com/status.html").then(function (rc) {
    response.say(rc.statusText);
  });
});

Customizing Default Error Messages

app.messages.NO_INTENT_FOUND = "Why you called dat intent? I don't know bout dat";

See the code for default messages you can override.

Read/write session data

app.launch(function(request, response) {
  request.getSession().set("number", 42);
  response.say("Would you like to know the number?");
  response.shouldEndSession(false);
});

app.intent("tellme", function(request, response) {
  var session = request.getSession();
  response.say("The number is " + session.get("number"));
  // clear only the 'number' attribute from the session
  session.clear("number");
});

// the session variables can be entirely cleared, or cleared by key
app.intent("clear", function(request, response) {
  var session = request.getSession();
  session.clear(); // or: session.clear("key") to clear a single value
  response.say("Session cleared!");
});

By default, alexa-app will persist every request session attribute into the response. This way, any session attributes you set will be sent on every subsequent request, as is typical in most web programming environments. If you wish to disable this feature, you can do so by setting app.persistentSession to false.

var app = new alexa.app("test");
app.persistentSession = false;

Define a custom endpoint name for an app

When mapped to express, the default endpoint for each app is the name of the app. You can customize this using the second parameter to the app() method.

var app = new alexa.app("hello", "myEndpointName");

All named apps can be found in the alexa.apps object, keyed by name. The value is the app itself.

License

Copyright (c) 2016-2017 Matt Kruse

MIT License, see LICENSE for details.

alexa-app's People

Contributors

dblock avatar joshskeen avatar matt-kruse avatar rickwargo avatar tejashah88 avatar mreinstein avatar trayburn avatar ajcrites avatar fremail avatar opendog avatar overloadut avatar jpriebe avatar nijotz avatar beeme1mr avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.