voxa
Version:
A fsm (state machine) framework for Alexa, Dialogflow, Facebook Messenger and Botframework apps using Node.js
549 lines • 18.4 kB
JavaScript
"use strict";
/*
* Copyright (c) 2018 Rain Agency <contact@rain.agency>
* Author: Rain Agency <contact@rain.agency>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const _ = require("lodash");
function isCard(card) {
if (!("type" in card)) {
return false;
}
return _.includes(["Standard", "Simple", "LinkAccount", "AskForPermissionsConsent"], card.type);
}
class AlexaDirective {
addDirective(reply) {
const response = reply.response;
if (!response.directives) {
response.directives = [];
}
if (!this.directive) {
throw new Error("The directive can't be empty");
}
if (_.isArray(this.directive)) {
response.directives = _.concat(response.directives, this.directive);
}
else {
response.directives.push(this.directive);
}
}
}
exports.AlexaDirective = AlexaDirective;
class MultimediaAlexaDirective extends AlexaDirective {
validateReply(reply) {
if (reply.hasDirective("AudioPlayer.Play")) {
throw new Error("Do not include both an AudioPlayer.Play" +
" directive and a VideoApp.Launch directive in the same response");
}
}
}
exports.MultimediaAlexaDirective = MultimediaAlexaDirective;
class HomeCard {
constructor(viewPath) {
this.viewPath = viewPath;
}
async writeToReply(reply, event, transition) {
if (reply.hasDirective("card")) {
throw new Error("At most one card can be specified in a response");
}
let card;
if (_.isString(this.viewPath)) {
card = await event.renderer.renderPath(this.viewPath, event);
if (!isCard(card)) {
throw new Error("The view should return a Card like object");
}
}
else if (isCard(this.viewPath)) {
card = this.viewPath;
}
else {
throw new Error("Argument should be a viewPath or a Card like object");
}
reply.response.card = card;
}
}
HomeCard.platform = "alexa";
HomeCard.key = "alexaCard";
exports.HomeCard = HomeCard;
class Hint {
constructor(viewPath) {
this.viewPath = viewPath;
}
async writeToReply(reply, event, transition) {
if (reply.hasDirective("Hint")) {
throw new Error("At most one Hint directive can be specified in a response");
}
const response = reply.response || {};
if (!response.directives) {
response.directives = [];
}
const text = await event.renderer.renderPath(this.viewPath, event);
response.directives.push({
hint: {
text,
type: "PlainText",
},
type: "Hint",
});
reply.response = response;
}
}
Hint.platform = "alexa";
Hint.key = "alexaHint";
exports.Hint = Hint;
class DialogDelegate extends AlexaDirective {
constructor(slots) {
super();
this.slots = slots;
}
async writeToReply(reply, event, transition) {
this.buildDirective(event);
this.buildSlots(event);
this.addDirective(reply);
}
buildSlots(event) {
if (!event.intent) {
throw new Error("An intent is required");
}
if (!this.slots) {
return;
}
const directiveSlots = _(this.slots)
.map((value, key) => {
const data = {
confirmationStatus: "NONE",
name: key,
};
if (value) {
data.value = value;
}
return [key, data];
})
.fromPairs()
.value();
this.directive.updatedIntent = {
confirmationStatus: "NONE",
name: event.intent.name,
slots: directiveSlots,
};
}
buildDirective(event) {
this.directive = {
type: "Dialog.Delegate",
};
}
}
DialogDelegate.platform = "alexa";
DialogDelegate.key = "alexaDialogDelegate";
exports.DialogDelegate = DialogDelegate;
class DialogElicitSlot extends AlexaDirective {
constructor(options) {
super();
this.options = options;
}
static validate(options, reply, event, transition) {
if (reply.hasDirective("Dialog.ElicitSlot")) {
throw new Error("At most one Dialog.ElicitSlot directive can be specified in a response");
}
if (transition.to &&
transition.to !== "die" &&
transition.to !== _.get(event, "rawEvent.request.intent.name")) {
throw new Error("You cannot transition to a new intent while using a Dialog.ElicitSlot directive");
}
if (!options.slotToElicit) {
throw new Error("slotToElicit is required for the Dialog.ElicitSlot directive");
}
if (!_.has(event, "rawEvent.request.dialogState") ||
_.get(event, "rawEvent.request.dialogState") === "COMPLETED") {
throw new Error("Intent is missing dialogState or has already completed this dialog and cannot elicit any slots");
}
}
async writeToReply(reply, event, transition) {
DialogElicitSlot.validate(this.options, reply, event, transition);
this.buildDirective(event);
// Alexa is always going to return to this intent with the results of this dialog
// so we can't move anywhere else.
transition.flow = "yield";
transition.to = _.get(event, "rawEvent.request.intent.name");
this.addDirective(reply);
}
buildDirective(event) {
const intent = _.get(event, "rawEvent.request.intent");
const slots = intent.slots;
if (this.options.slots) {
_.forOwn(this.options.slots, (value, key) => {
if (_.has(slots, key)) {
if (!_.has(value, "name")) {
_.set(value, "name", key);
}
slots[key] = value;
}
});
}
this.directive = {
slotToElicit: this.options.slotToElicit,
type: "Dialog.ElicitSlot",
updatedIntent: {
confirmationStatus: "NONE",
name: intent.name,
slots,
},
};
}
}
DialogElicitSlot.platform = "alexa";
DialogElicitSlot.key = "alexaElicitDialog";
exports.DialogElicitSlot = DialogElicitSlot;
class RenderTemplate extends AlexaDirective {
constructor(viewPath) {
super();
if (_.isString(viewPath)) {
this.viewPath = viewPath;
}
else {
this.directive = viewPath;
}
}
async writeToReply(reply, event, transition) {
this.validateReply(reply);
if (!_.includes(event.supportedInterfaces, "Display")) {
return;
}
if (this.viewPath) {
this.directive = await event.renderer.renderPath(this.viewPath, event);
}
this.addDirective(reply);
}
validateReply(reply) {
if (reply.hasDirective("Display.RenderTemplate")) {
throw new Error("At most one Display.RenderTemplate directive can be specified in a response");
}
}
}
RenderTemplate.key = "alexaRenderTemplate";
RenderTemplate.platform = "alexa";
exports.RenderTemplate = RenderTemplate;
class APLTemplate extends AlexaDirective {
constructor(viewPath) {
super();
if (_.isString(viewPath)) {
this.viewPath = viewPath;
}
else {
this.directive = viewPath;
}
}
async writeToReply(reply, event, transition) {
this.validateReply(reply);
if (!_.includes(event.supportedInterfaces, "Alexa.Presentation.APL")) {
return;
}
if (this.viewPath) {
this.directive = await event.renderer.renderPath(this.viewPath, event);
}
this.addDirective(reply);
}
validateReply(reply) {
if (reply.hasDirective("Alexa.Presentation.APL.RenderDocument")) {
throw new Error("At most one Alexa.Presentation.APL.RenderDocument directive can be specified in a response");
}
}
}
APLTemplate.key = "alexaAPLTemplate";
APLTemplate.platform = "alexa";
exports.APLTemplate = APLTemplate;
class APLCommand extends AlexaDirective {
constructor(viewPath) {
super();
if (_.isString(viewPath)) {
this.viewPath = viewPath;
}
else {
this.directive = viewPath;
}
}
async writeToReply(reply, event, transition) {
this.validateReply(reply);
if (!_.includes(event.supportedInterfaces, "Alexa.Presentation.APL")) {
return;
}
if (this.viewPath) {
this.directive = await event.renderer.renderPath(this.viewPath, event);
}
this.addDirective(reply);
}
validateReply(reply) {
if (reply.hasDirective("Alexa.Presentation.APL.ExecuteCommands")) {
throw new Error("At most one Alexa.Presentation.APL.ExecuteCommands directive can be specified in a response");
}
}
}
APLCommand.key = "alexaAPLCommand";
APLCommand.platform = "alexa";
exports.APLCommand = APLCommand;
class APLTTemplate extends AlexaDirective {
constructor(viewPath) {
super();
if (_.isString(viewPath)) {
this.viewPath = viewPath;
}
else {
this.directive = viewPath;
}
}
async writeToReply(reply, event, transition) {
this.validateReply(reply);
if (!_.includes(event.supportedInterfaces, "Alexa.Presentation.APLT")) {
return;
}
if (this.viewPath) {
this.directive = await event.renderer.renderPath(this.viewPath, event);
}
this.addDirective(reply);
}
validateReply(reply) {
if (reply.hasDirective("Alexa.Presentation.APLT.RenderDocument")) {
throw new Error("At most one Alexa.Presentation.APLT.RenderDocument directive can be specified in a response");
}
}
}
APLTTemplate.key = "alexaAPLTTemplate";
APLTTemplate.platform = "alexa";
exports.APLTTemplate = APLTTemplate;
class APLTCommand extends AlexaDirective {
constructor(viewPath) {
super();
if (_.isString(viewPath)) {
this.viewPath = viewPath;
}
else {
this.directive = viewPath;
}
}
async writeToReply(reply, event, transition) {
this.validateReply(reply);
if (!_.includes(event.supportedInterfaces, "Alexa.Presentation.APLT")) {
return;
}
if (this.viewPath) {
this.directive = await event.renderer.renderPath(this.viewPath, event);
}
this.addDirective(reply);
}
validateReply(reply) {
if (reply.hasDirective("Alexa.Presentation.APLT.ExecuteCommands")) {
throw new Error("At most one Alexa.Presentation.APLT.ExecuteCommands directive can be specified in a response");
}
}
}
APLTCommand.key = "alexaAPLTCommand";
APLTCommand.platform = "alexa";
exports.APLTCommand = APLTCommand;
class AccountLinkingCard {
async writeToReply(reply, event, transition) {
if (reply.hasDirective("card")) {
throw new Error("At most one card can be specified in a response");
}
const card = { type: "LinkAccount" };
reply.response.card = card;
}
}
AccountLinkingCard.key = "alexaAccountLinkingCard";
AccountLinkingCard.platform = "alexa";
exports.AccountLinkingCard = AccountLinkingCard;
class PlayAudio extends MultimediaAlexaDirective {
constructor(data) {
super();
this.data = data;
}
async writeToReply(reply, event, transition) {
this.validateReply(reply);
this.directive = {
audioItem: {
metadata: this.data.metadata || {},
stream: {
offsetInMilliseconds: this.data.offsetInMilliseconds || 0,
token: this.data.token,
url: this.data.url,
},
},
playBehavior: this.data.behavior || "REPLACE_ALL",
type: "AudioPlayer.Play",
};
this.addDirective(reply);
}
}
PlayAudio.key = "alexaPlayAudio";
PlayAudio.platform = "alexa";
exports.PlayAudio = PlayAudio;
class StopAudio extends AlexaDirective {
constructor() {
super(...arguments);
this.directive = {
type: "AudioPlayer.Stop",
};
}
async writeToReply(reply, event, transition) {
this.addDirective(reply);
}
}
StopAudio.key = "alexaStopAudio";
StopAudio.platform = "alexa";
exports.StopAudio = StopAudio;
class GadgetControllerLightDirective extends AlexaDirective {
constructor(directive) {
super();
this.directive = directive;
}
async writeToReply(reply, event, transition) {
this.addDirective(reply);
}
}
GadgetControllerLightDirective.key = "alexaGadgetControllerLightDirective";
GadgetControllerLightDirective.platform = "alexa";
exports.GadgetControllerLightDirective = GadgetControllerLightDirective;
class GameEngineStartInputHandler extends AlexaDirective {
constructor(directive) {
super();
this.directive = directive;
}
async writeToReply(reply, event, transition) {
this.addDirective(reply);
const response = reply.response;
delete response.shouldEndSession;
}
}
GameEngineStartInputHandler.key = "alexaGameEngineStartInputHandler";
GameEngineStartInputHandler.platform = "alexa";
exports.GameEngineStartInputHandler = GameEngineStartInputHandler;
class GameEngineStopInputHandler extends AlexaDirective {
constructor(originatingRequestId) {
super();
this.originatingRequestId = originatingRequestId;
}
async writeToReply(reply, event, transition) {
this.directive = {
originatingRequestId: this.originatingRequestId,
type: "GameEngine.StopInputHandler",
};
this.addDirective(reply);
}
}
GameEngineStopInputHandler.key = "alexaGameEngineStopInputHandler";
GameEngineStopInputHandler.platform = "alexa";
exports.GameEngineStopInputHandler = GameEngineStopInputHandler;
class ConnectionsSendRequest extends AlexaDirective {
constructor(name, payload, token) {
super();
this.payload = payload;
this.token = token;
this.type = "Connections.SendRequest";
if (_.isString(name)) {
this.name = name;
}
else {
this.directive = name;
}
}
async writeToReply(reply, event, transition) {
if (this.name) {
this.directive = {
name: this.name,
payload: this.payload,
token: this.token || "token",
type: "Connections.SendRequest",
};
}
this.addDirective(reply);
}
}
ConnectionsSendRequest.key = "alexaConnectionsSendRequest";
ConnectionsSendRequest.platform = "alexa";
exports.ConnectionsSendRequest = ConnectionsSendRequest;
class VideoAppLaunch extends MultimediaAlexaDirective {
constructor(options) {
super();
this.options = options;
}
async writeToReply(reply, event, transition) {
this.validateReply(reply);
if (!_.includes(event.supportedInterfaces, "VideoApp")) {
return;
}
let options;
if (_.isString(this.options)) {
options = await event.renderer.renderPath(this.options, event);
}
else {
options = this.options;
}
this.directive = {
type: "VideoApp.Launch",
videoItem: {
metadata: {
subtitle: options.subtitle,
title: options.title,
},
source: options.source,
},
};
this.addDirective(reply);
}
}
VideoAppLaunch.key = "alexaVideoAppLaunch";
VideoAppLaunch.platform = "alexa";
exports.VideoAppLaunch = VideoAppLaunch;
class DynamicEntitiesDirective extends AlexaDirective {
constructor(viewPath) {
super();
if (_.isString(viewPath)) {
this.viewPath = viewPath;
}
else if (_.isArray(viewPath)) {
this.types = viewPath;
}
else {
this.directive = viewPath;
}
}
async writeToReply(reply, event, transition) {
let types = [];
if (this.viewPath) {
types = await event.renderer.renderPath(this.viewPath, event);
this.directive = {
type: "Dialog.UpdateDynamicEntities",
types,
updateBehavior: "REPLACE",
};
}
if (this.types) {
this.directive = {
type: "Dialog.UpdateDynamicEntities",
types: this.types,
updateBehavior: "REPLACE",
};
}
this.addDirective(reply);
}
}
DynamicEntitiesDirective.key = "alexaDynamicEntities";
DynamicEntitiesDirective.platform = "alexa";
exports.DynamicEntitiesDirective = DynamicEntitiesDirective;
//# sourceMappingURL=directives.js.map