alexa-app
Version:
A module to simplify creation of Alexa (Amazon Echo) apps (Skills) using Node.js
939 lines (866 loc) • 30.5 kB
JavaScript
"use strict";
var Promise = require("bluebird");
var AlexaUtterances = require("alexa-utterances");
var SSML = require("./lib/to-ssml");
var alexa = {};
var defaults = require("lodash.defaults");
var verifier = require("alexa-verifier-middleware");
var bodyParser = require('body-parser');
var normalizeApiPath = require('./lib/normalize-api-path');
alexa.response = function(session) {
var self = this;
this.resolved = false;
this.response = {
"version": "1.0",
"response": {
"directives": [],
"shouldEndSession": true
}
};
this.say = function(str) {
if (typeof this.response.response.outputSpeech == "undefined") {
this.response.response.outputSpeech = {
"type": "SSML",
"ssml": SSML.fromStr(str)
};
} else {
// append str to the current outputSpeech, stripping the out speak tag
this.response.response.outputSpeech.ssml = SSML.fromStr(str, this.response.response.outputSpeech.ssml);
}
return this;
};
this.clear = function( /*str*/ ) {
this.response.response.outputSpeech = {
"type": "SSML",
"ssml": SSML.fromStr("")
};
return this;
};
this.reprompt = function(str) {
if (typeof this.response.response.reprompt == "undefined") {
this.response.response.reprompt = {
"outputSpeech": {
"type": "SSML",
"ssml": SSML.fromStr(str)
}
};
} else {
// append str to the current outputSpeech, stripping the out speak tag
this.response.response.reprompt.outputSpeech.ssml = SSML.fromStr(str, this.response.response.reprompt.outputSpeech.ssml);
}
return this;
};
this.card = function(oCard) {
if (2 == arguments.length) { // backwards compat
oCard = {
type: "Simple",
title: arguments[0],
content: arguments[1]
};
}
var requiredAttrs = ['type'],
clenseAttrs = [];
switch (oCard.type) {
case 'Simple':
requiredAttrs.push('content');
clenseAttrs.push('content');
break;
case 'Standard':
requiredAttrs.push('text');
clenseAttrs.push('text');
if (('image' in oCard) && (!('smallImageUrl' in oCard['image']) && !('largeImageUrl' in oCard['image']))) {
console.error('If card.image is defined, must specify at least smallImageUrl or largeImageUrl');
return this;
}
break;
case 'AskForPermissionsConsent':
requiredAttrs.push('permissions');
break;
default:
break;
}
var hasAllReq = requiredAttrs.every(function(idx) {
if (!(idx in oCard) || typeof oCard[idx] === 'undefined') {
console.error('Card object is missing required attr "' + idx + '"');
return false;
}
return true;
});
if (!hasAllReq) {
return this;
}
// remove all SSML to keep the card clean
clenseAttrs.forEach(function(idx) {
oCard[idx] = SSML.cleanse(oCard[idx]);
});
this.response.response.card = oCard;
return this;
};
this.linkAccount = function() {
this.response.response.card = {
"type": "LinkAccount"
};
return this;
};
this.shouldEndSession = function(bool, reprompt) {
if (bool === null || typeof bool == "undefined") {
delete this.response.response.shouldEndSession;
} else {
this.response.response.shouldEndSession = bool;
}
if (reprompt) {
this.reprompt(reprompt);
}
return this;
};
this.sessionObject = session;
this.setSessionAttributes = function(attributes) {
this.response.sessionAttributes = attributes;
};
// prepare response object
this.prepare = function() {
this.setSessionAttributes(this.sessionObject.getAttributes());
};
this.audioPlayerPlay = function(playBehavior, audioItem) {
var audioPlayerDirective = {
"type": "AudioPlayer.Play",
"playBehavior": playBehavior,
"audioItem": audioItem
};
this.directive(audioPlayerDirective);
return this;
};
this.audioPlayerPlayStream = function(playBehavior, stream) {
var audioItem = {
"stream": stream
};
return this.audioPlayerPlay(playBehavior, audioItem);
};
this.audioPlayerStop = function() {
var audioPlayerDirective = {
"type": "AudioPlayer.Stop"
};
this.directive(audioPlayerDirective);
return this;
};
this.audioPlayerClearQueue = function(clearBehavior) {
var audioPlayerDirective = {
"type": "AudioPlayer.ClearQueue",
"clearBehavior": clearBehavior || "CLEAR_ALL"
};
this.directive(audioPlayerDirective);
return this;
};
// Read & manipulate response directives
var directives = new alexa.directives(self.response.response.directives);
this.getDirectives = function() {
return directives;
};
this.directive = function(directive) {
this.getDirectives().set(directive);
return this;
};
// legacy code below
// @deprecated
this.session = function(key, val) {
if (typeof val == "undefined") {
return this.sessionObject.get(key);
} else {
this.sessionObject.set(key, val);
}
return this;
};
// @deprecated
this.clearSession = function(key) {
this.sessionObject.clear(key);
return this;
};
};
alexa.directives = function(directives) {
// load the alexa response directives information into details
this.details = directives;
this.set = function(directive) {
this.details.push(directive);
};
this.clear = function() {
this.details.length = 0;
};
};
alexa.request = function(json) {
this.data = json;
this.slots = {};
if (this.data.request && this.data.request.intent && this.data.request.intent.slots && Object.keys(this.data.request.intent.slots).length > 0) {
var slot, slotName;
for (slotName in this.data.request.intent.slots) {
slot = new alexa.slot(this.data.request.intent.slots[slotName]);
this.slots[slotName] = slot;
}
}
this.slot = function(slotName, defaultValue) {
if (this.slots && 'undefined' != typeof this.slots[slotName] && 'undefined' != typeof this.slots[slotName].value) {
return this.slots[slotName].value;
} else {
return defaultValue;
}
};
this.type = function() {
if (!(this.data && this.data.request && this.data.request.type)) {
console.error("missing request type:", this.data);
return;
}
return this.data.request.type;
};
this.isAudioPlayer = function() {
var requestType = this.type();
return (requestType && 0 === requestType.indexOf("AudioPlayer."));
};
this.isPlaybackController = function() {
var requestType = this.type();
return (requestType && 0 === requestType.indexOf("PlaybackController."));
};
if (this.data.request && this.data.request.intent) {
this.confirmationStatus = this.data.request.intent.confirmationStatus;
}
if (this.data.request && this.data.request.type === 'Display.ElementSelected' && this.data.request.token) {
this.selectedElementToken = this.data.request.token;
}
this.isConfirmed = function() {
return 'CONFIRMED' === this.confirmationStatus;
};
this.userId = null;
this.applicationId = null;
this.context = null;
if (this.data.context) {
this.userId = this.data.context.System.user.userId;
this.applicationId = this.data.context.System.application.applicationId;
this.context = this.data.context;
}
var session = new alexa.session(json.session);
this.hasSession = function() {
return session.isAvailable();
};
this.getSession = function() {
return session;
};
this.getDialog = function() {
var dialogState = (typeof this.data.request['dialogState'] !== "undefined") ?
this.data.request['dialogState'] : null;
return new alexa.dialog(dialogState);
};
// legacy code below
// @deprecated
this.sessionDetails = this.getSession().details;
// @deprecated
this.sessionId = this.getSession().sessionId;
// @deprecated
this.sessionAttributes = this.getSession().attributes;
// @deprecated
this.isSessionNew = this.hasSession() ? this.getSession().isNew() : false;
// @deprecated
this.session = function(key) {
return this.getSession().get(key);
};
};
alexa.dialog = function(dialogState) {
this.dialogState = dialogState;
this.isStarted = function() {
return 'STARTED' === this.dialogState;
};
this.isInProgress = function() {
return 'IN_PROGRESS' === this.dialogState;
};
this.isCompleted = function() {
return 'COMPLETED' === this.dialogState;
};
this.handleDialogDelegation = function(request, response) {
var dialogDirective = {
"type": "Dialog.Delegate"
};
response.shouldEndSession(false).directive(dialogDirective).send();
};
};
alexa.intent = function(name, schema, handler) {
this.name = name;
this.handler = handler;
this.dialog = (schema && typeof schema.dialog !== "undefined") ? schema.dialog : {};
this.slots = (schema && typeof schema["slots"] !== "undefined") ? schema["slots"] : null;
this.utterances = (schema && typeof schema["utterances"] !== "undefined") ? schema["utterances"] : null;
this.isDelegatedDialog = function() {
return this.dialog.type === "delegate";
};
};
alexa.slot = function(slot) {
this.name = slot.name;
this.value = slot.value;
this.confirmationStatus = slot.confirmationStatus;
if (slot.resolutions && slot.resolutions.resolutionsPerAuthority && slot.resolutions.resolutionsPerAuthority.length > 0) {
this.resolutions = slot.resolutions.resolutionsPerAuthority.map(function(resolution) {
return new alexa.slotResolution(resolution);
});
} else {
this.resolutions = [];
}
this.isConfirmed = function() {
return 'CONFIRMED' === this.confirmationStatus;
};
this.resolution = function(idx) {
idx = ( typeof idx === 'number' && idx >= 0 && idx < this.resolutions.length ) ? idx : 0;
return this.resolutions[idx];
};
};
alexa.slotResolution = function(resolution) {
this.status = resolution.status.code;
this.values = (resolution.values || []).map(function(elem) {
return new alexa.resolutionValue(elem.value);
});
this.isMatched = function() {
return 'ER_SUCCESS_MATCH' === this.status;
};
this.first = function() {
return this.values[0];
};
};
alexa.resolutionValue = function(value) {
this.name = value.name;
this.id = value.id;
};
alexa.session = function(session) {
var isAvailable = (typeof session != "undefined");
this.isAvailable = function() {
return isAvailable;
};
if (isAvailable) {
this.isNew = function() {
return (true === session.new);
};
this.get = function(key) {
// getAttributes deep clones the attributes object, so updates to objects
// will not affect the session until `set` is called explicitly
return this.getAttributes()[key];
};
this.set = function(key, value) {
this.attributes[key] = value;
};
this.clear = function(key) {
if (typeof key == "string") {
if (typeof this.attributes[key] != "undefined") {
delete this.attributes[key];
}
} else {
this.attributes = {};
}
};
// load the alexa session information into details
this.details = session;
// @deprecated
this.details.userId = this.details.user.userId || null;
// @deprecated
this.details.accessToken = this.details.user.accessToken || null;
// persist all the session attributes across requests
// the Alexa API doesn't think session variables should persist for the entire
// duration of the session, but I do
this.attributes = session.attributes || {};
this.sessionId = session.sessionId;
} else {
this.isNew = this.get = this.set = this.clear = function() {
throw "NO_SESSION";
};
this.details = {};
this.attributes = {};
this.sessionId = null;
}
this.getAttributes = function() {
// deep clone attributes so direct updates to objects are not set in the
// session unless `.set` is called explicitly
return JSON.parse(JSON.stringify(this.attributes));
};
};
alexa.router = function(app, request, response, request_json) {
this.intent = function(intent) {
if (typeof app.intents[intent] !== "undefined" && typeof app.intents[intent].handler === "function") {
if (app.intents[intent].isDelegatedDialog() && !request.getDialog().isCompleted()) {
return Promise.resolve(request.getDialog().handleDialogDelegation(request, response));
} else {
return Promise.resolve(app.intents[intent].handler(request, response));
}
} else {
throw "NO_INTENT_FOUND";
}
};
this.launch = function() {
if (typeof app.launchFunc === "function") {
return Promise.resolve(app.launchFunc(request, response));
} else {
throw "NO_LAUNCH_FUNCTION";
}
};
this.sessionEnded = function() {
if (typeof app.sessionEndedFunc === "function") {
return Promise.resolve(app.sessionEndedFunc(request, response));
}
};
this.audioPlayer = function(event) {
var eventHandlerObject = app.audioPlayerEventHandlers[event];
if (typeof eventHandlerObject !== "undefined" && typeof eventHandlerObject["function"] === "function") {
return Promise.resolve(eventHandlerObject["function"](request, response));
}
};
this.playbackController = function(event) {
var playbackEventHandlerObject = app.playbackControllerEventHandlers[event];
if (typeof playbackEventHandlerObject !== "undefined" && typeof playbackEventHandlerObject["function"] === "function") {
return Promise.resolve(playbackEventHandlerObject["function"](request, response));
}
};
this.displayElementSelected = function() {
if (typeof app.displayElementSelectedFunc === "function") {
return Promise.resolve(app.displayElementSelectedFunc(request, response));
} else {
throw "NO_DISPLAY_ELEMENT_SELECTED_FUNCTION";
}
};
this.custom = function(requestType) {
if (typeof app.requestHandlers[requestType] === "function") {
return Promise.resolve(app.requestHandlers[requestType](request, response, request_json));
} else {
throw "NO_CUSTOM_REQUEST_HANDLER";
}
};
};
alexa.apps = {};
alexa.app = function(name) {
if (!(this instanceof alexa.app)) {
throw new Error("Function must be called with the new keyword");
}
var self = this;
this.name = name;
this.messages = {
// when an intent was passed in that the application was not configured to handle
"NO_INTENT_FOUND": "Sorry, the application didn't know what to do with that intent",
// when an AudioPlayer event was passed in that the application was not configured to handle
"NO_AUDIO_PLAYER_EVENT_HANDLER_FOUND": "Sorry, the application didn't know what to do with that AudioPlayer event",
// when the app was used with 'open' or 'launch' but no launch handler was defined
"NO_LAUNCH_FUNCTION": "Try telling the application what to do instead of opening it",
// when a request type was not recognized
"INVALID_REQUEST_TYPE": "Error: not a valid request",
// when a request was routed to custom handler, but no such handler was defined
"NO_CUSTOM_REQUEST_HANDLER": "Error: no custom request handler found",
// when a request and response don't contain session object
// https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interface-reference#request-body-parameters
"NO_SESSION": "This request doesn't support session attributes",
// if some other exception happens
"GENERIC_ERROR": "Sorry, the application encountered an error",
// User interacted with the display but no element selected handler has been defined.
"NO_DISPLAY_ELEMENT_SELECTED_FUNCTION": "Try telling the application how to handle display events. Make sure displayElementSelected is implemented."
};
// persist session variables from every request into every response
this.persistentSession = true;
// use a minimal set of utterances or the full cartesian product
this.exhaustiveUtterances = false;
// a catch-all error handler do nothing by default
this.error = null;
// pre/post hooks to be run on every request
this.pre = function( /*request, response, type*/ ) {};
this.post = function( /*request, response, type*/ ) {};
// a mapping of keywords to arrays of possible values, for expansion of sample utterances
this.dictionary = {};
this.intents = {};
this.intent = function(intentName, schema, func) {
if (typeof schema === "function") {
func = schema;
schema = {};
}
self.intents[intentName] = new alexa.intent(intentName, schema, func);
};
// handle custom/future request types
this.requestHandlers = {};
this.on = function(handlerName, handler) {
self.requestHandlers[handlerName] = handler;
};
this.customSlots = {};
this.customSlot = function(slotName, values) {
self.customSlots[slotName] = [];
values.forEach(function(value) {
var valueObj;
if (typeof value === "string") {
valueObj = {
value: value,
id: null,
synonyms: []
};
} else {
valueObj = {
value: value.value,
id: value.id || null,
synonyms: []
};
if (value.synonyms) {
value.synonyms.forEach(function(sample) {
var list = AlexaUtterances(sample,
null,
self.dictionary,
self.exhaustiveUtterances);
list.forEach(function(utterance) {
valueObj.synonyms.push(utterance);
});
});
}
}
self.customSlots[slotName].push(valueObj);
});
};
this.audioPlayerEventHandlers = {};
this.audioPlayer = function(eventName, func) {
self.audioPlayerEventHandlers[eventName] = {
"name": eventName,
"function": func
};
};
this.playbackControllerEventHandlers = {};
this.playbackController = function(eventName, func) {
self.playbackControllerEventHandlers[eventName] = {
"name": eventName,
"function": func
};
};
this.launchFunc = null;
this.launch = function(func) {
self.launchFunc = func;
};
this.displayElementSelectedFunc = null;
this.displayElementSelected = function(func) {
self.displayElementSelectedFunc = func;
};
this.sessionEndedFunc = null;
this.sessionEnded = function(func) {
self.sessionEndedFunc = func;
};
this.request = function(request_json) {
var request = new alexa.request(request_json);
var response = new alexa.response(request.getSession());
var router = new alexa.router(self, request, response, request_json);
var postExecuted = false;
var requestType = request.type();
var promiseChain = Promise.resolve();
request.getRouter = function() {
return router;
};
// attach Promise resolve/reject functions to the response object
response.send = function(exception) {
response.prepare();
var postPromise = Promise.resolve();
if (typeof self.post == "function" && !postExecuted) {
postExecuted = true;
postPromise = Promise.resolve(self.post(request, response, requestType, exception));
}
return postPromise.then(function() {
response.prepare();
if (!response.resolved) {
response.resolved = true;
}
return response.response;
});
};
response.fail = function(msg, exception) {
response.prepare();
var postPromise = Promise.resolve();
if (typeof self.post == "function" && !postExecuted) {
postExecuted = true;
postPromise = Promise.resolve(self.post(request, response, requestType, exception));
}
return postPromise.then(function() {
response.prepare();
if (!response.resolved) {
response.resolved = true;
throw msg;
}
// propagate successful response if it's already been resolved
return response.response;
});
};
return promiseChain.then(function() {
// Call to `.pre` can also throw, so we wrap it in a promise here to
// propagate errors to the error handler
var prePromise = Promise.resolve();
if (typeof self.pre == "function") {
prePromise = Promise.resolve(self.pre(request, response, requestType));
}
return prePromise;
}).then(function() {
requestType = request.type();
if (!response.resolved) {
if ("IntentRequest" === requestType) {
var intent = request_json.request.intent.name;
return request.getRouter().intent(intent);
} else if ("LaunchRequest" === requestType) {
return request.getRouter().launch();
} else if ("SessionEndedRequest" === requestType) {
return request.getRouter().sessionEnded();
} else if (request.isAudioPlayer()) {
var event = requestType.slice(12);
return request.getRouter().audioPlayer(event);
} else if (request.isPlaybackController()) {
var playbackControllerEvent = requestType.slice(19);
return request.getRouter().playbackController(playbackControllerEvent);
} else if ("Display.ElementSelected" === requestType) {
return request.getRouter().displayElementSelected();
} else if (typeof self.requestHandlers[requestType] === "function") {
return request.getRouter().custom(requestType);
} else {
throw "INVALID_REQUEST_TYPE";
}
}
})
.then(function() {
return response.send();
})
.catch(function(e) {
if (typeof self.error == "function") {
// Default behavior of any error handler is to send a response
return Promise.resolve(self.error(e, request, response)).then(function() {
if (!response.resolved) {
response.resolved = true;
return response.send();
}
// propagate successful response if it's already been resolved
return response.response;
});
} else if (typeof e == "string" && self.messages[e]) {
if (!request.isAudioPlayer()) {
response.say(self.messages[e]);
return response.send(e);
} else {
return response.fail(self.messages[e]);
}
}
if (!response.resolved) {
if (e.message) {
return response.fail("Unhandled exception: " + e.message + ".", e);
} else if (typeof e == "string") {
return response.fail("Unhandled exception: " + e + ".", e);
} else {
return response.fail("Unhandled exception.", e);
}
}
throw e;
});
};
var skillBuilderSchema = function() {
var schema = {
"intents": [],
"types": []
},
intentName, intent, key;
for (intentName in self.intents) {
intent = self.intents[intentName];
var intentSchema = {
"name": intent.name,
"samples": []
};
if (intent.utterances && intent.utterances.length > 0) {
intent.utterances.forEach(function(sample) {
var list = AlexaUtterances(sample,
intent.slots,
self.dictionary,
self.exhaustiveUtterances);
list.forEach(function(utterance) {
intentSchema.samples.push(utterance);
});
});
}
if (intent.slots && Object.keys(intent.slots).length > 0) {
intentSchema["slots"] = [];
for (key in intent.slots) {
// It's unclear whether `samples` is actually used for slots,
// but the interaction model will not build without an (empty) array
intentSchema.slots.push({
"name": key,
"type": intent.slots[key],
"samples": []
});
}
}
schema.intents.push(intentSchema);
}
for (var slotName in self.customSlots) {
var slotSchema = {
name: slotName,
values: []
};
var values = self.customSlots[slotName];
values.forEach(function(value) {
var valueSchema = {
"id": value.id,
"name": {
"value": value.value,
"synonyms": value.synonyms
}
};
slotSchema.values.push(valueSchema);
});
schema.types.push(slotSchema);
}
return schema;
};
this.schemas = {
intent: function() {
var schema = {
"intents": []
},
intentName, intent, key;
for (intentName in self.intents) {
intent = self.intents[intentName];
var intentSchema = {
"intent": intent.name
};
if (intent.slots && Object.keys(intent.slots).length > 0) {
intentSchema["slots"] = [];
for (key in intent.slots) {
intentSchema.slots.push({
"name": key,
"type": intent.slots[key]
});
}
}
schema.intents.push(intentSchema);
}
return JSON.stringify(schema, null, 3);
},
skillBuilder: function() {
var schema = skillBuilderSchema();
return JSON.stringify(schema, null, 3);
},
askcli: function(invocationName) {
var model = skillBuilderSchema();
model.invocationName = invocationName || self.invocationName || self.name;
var schema = {
interactionModel: {
languageModel: model
}
};
return JSON.stringify(schema, null, 3);
}
};
// extract the schema and generate a schema JSON object
this.schema = function() {
return this.schemas.intent();
};
// generate a list of sample utterances
this.utterances = function() {
var intentName,
intent,
out = "";
for (intentName in self.intents) {
intent = self.intents[intentName];
if (intent.utterances) {
intent.utterances.forEach(function(sample) {
var list = AlexaUtterances(sample,
intent.slots,
self.dictionary,
self.exhaustiveUtterances);
list.forEach(function(utterance) {
out += intent.name + " " + (utterance.replace(/\s+/g, " ")).trim() + "\n";
});
});
}
}
return out;
};
// a built-in handler for AWS Lambda
this.handler = function(event, context, callback) {
self.request(event)
.then(function(response) {
callback(null, response);
})
.catch(function(response) {
callback(response);
});
};
// for backwards compatibility
this.lambda = function() {
return self.handler;
};
// attach Alexa endpoint to an express router
//
// @param object options.expressApp the express instance to attach to
// @param router options.router router instance to attach to the express app
// @param string options.endpoint the path to attach the router to (e.g., passing 'mine' attaches to '/mine')
// @param bool options.checkCert when true, applies Alexa certificate checking (default true)
// @param bool options.debug when true, sets up the route to handle GET requests (default false)
// @param function options.preRequest function to execute before every POST
// @param function options.postRequest function to execute after every POST
// @throws Error when router or expressApp options are not specified
// @returns this
this.express = function(options) {
if (!options.expressApp && !options.router) {
throw new Error("You must specify an express app or an express router to attach to.");
}
var defaultOptions = {
endpoint: "/" + self.name,
checkCert: true,
debug: false
};
options = defaults(options, defaultOptions);
// In ExpressJS, user specifies their paths without the '/' prefix
var deprecated = options.expressApp && options.router;
var endpoint = deprecated ? '/' : normalizeApiPath(options.endpoint);
var target = deprecated ? options.router : (options.expressApp || options.router);
if (deprecated) {
options.expressApp.use(normalizeApiPath(options.endpoint), options.router);
console.warn("Usage deprecated: Both 'expressApp' and 'router' are specified.\nMore details on https://github.com/alexa-js/alexa-app/blob/master/UPGRADING.md");
}
if (options.debug) {
target.get(endpoint, function(req, res) {
var schemaName = req.query['schemaType'] || 'intent';
var schema = self.schemas[schemaName] || function() {};
if (typeof req.query['schema'] != "undefined") {
res.set('Content-Type', 'text/plain').send(schema());
} else if (typeof req.query['utterances'] != "undefined") {
res.set('Content-Type', 'text/plain').send(self.utterances());
} else {
res.render("test", {
"app": self,
"schema": schema(),
"utterances": self.utterances()
});
}
});
}
if (options.checkCert) {
target.use(endpoint, verifier);
} else {
target.use(endpoint, bodyParser.json());
}
// exposes POST /<endpoint> route
target.post(endpoint, function(req, res) {
var json = req.body,
response_json;
// preRequest and postRequest may return altered request JSON, or undefined, or a Promise
Promise.resolve(typeof options.preRequest == "function" ? options.preRequest(json, req, res) : json)
.then(function(json_new) {
if (json_new) {
json = json_new;
}
return json;
})
.then(self.request)
.then(function(app_response_json) {
response_json = app_response_json;
return Promise.resolve(typeof options.postRequest == "function" ? options.postRequest(app_response_json, req, res) : app_response_json);
})
.then(function(response_json_new) {
response_json = response_json_new || response_json;
res.json(response_json).send();
})
.catch(function(err) {
console.error(err);
res.status(500).send("Server Error");
});
});
};
// add the app to the global list of named apps
if (name) {
alexa.apps[name] = self;
}
return this;
};
module.exports = alexa;