voxa
Version:
A fsm (state machine) framework for Alexa, Dialogflow, Facebook Messenger and Botframework apps using Node.js
187 lines (167 loc) • 5.92 kB
text/typescript
/*
* 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.
*/
import { RequestEnvelope } from "ask-sdk-model";
import { Context as AWSLambdaContext } from "aws-lambda";
import { Context as AzureContext } from "azure-functions-ts-essentials";
import * as _ from "lodash";
import { OnSessionEndedError } from "../../errors";
import { VoxaApp } from "../../VoxaApp";
import { IVoxaEvent, IVoxaIntentEvent } from "../../VoxaEvent";
import { IVoxaReply } from "../../VoxaReply";
import { IVoxaPlatformConfig, VoxaPlatform } from "../VoxaPlatform";
import { AlexaEvent } from "./AlexaEvent";
import { AlexaReply } from "./AlexaReply";
import {
AccountLinkingCard,
APLCommand,
APLTCommand,
APLTemplate,
APLTTemplate,
ConnectionsSendRequest,
DialogDelegate,
DialogElicitSlot,
DynamicEntitiesDirective,
GadgetControllerLightDirective,
GameEngineStartInputHandler,
GameEngineStopInputHandler,
Hint,
HomeCard,
PlayAudio,
RenderTemplate,
StopAudio,
VideoAppLaunch,
} from "./directives";
const AlexaRequests = [
"AudioPlayer.PlaybackStarted",
"AudioPlayer.PlaybackFinished",
"AudioPlayer.PlaybackNearlyFinished",
"AudioPlayer.PlaybackStopped",
"AudioPlayer.PlaybackFailed",
"System.ExceptionEncountered",
"PlaybackController.NextCommandIssued",
"PlaybackController.PauseCommandIssued",
"PlaybackController.PlayCommandIssued",
"PlaybackController.PreviousCommandIssued",
"AlexaSkillEvent.ProactiveSubscriptionChanged",
"AlexaSkillEvent.SkillAccountLinked",
"AlexaSkillEvent.SkillEnabled",
"AlexaSkillEvent.SkillDisabled",
"AlexaSkillEvent.SkillPermissionAccepted",
"AlexaSkillEvent.SkillPermissionChanged",
"AlexaHouseholdListEvent.ItemsCreated",
"AlexaHouseholdListEvent.ItemsUpdated",
"AlexaHouseholdListEvent.ItemsDeleted",
"Connections.Response",
"Display.ElementSelected",
"CanFulfillIntentRequest",
"GameEngine.InputHandlerEvent",
"Alexa.Presentation.APL.UserEvent",
"Alexa.Presentation.APLT.UserEvent",
"Messaging.MessageReceived",
];
export interface IAlexaPlatformConfig extends IVoxaPlatformConfig {
appIds?: string | string[];
defaultFulfillIntents?: string[];
}
export class AlexaPlatform extends VoxaPlatform {
public name: string = "alexa";
public config: IAlexaPlatformConfig;
protected EventClass = AlexaEvent;
constructor(voxaApp: VoxaApp, config: IAlexaPlatformConfig = {}) {
super(voxaApp, config);
this.config = config;
this.app.onCanFulfillIntentRequest(
(event: IVoxaIntentEvent, reply: AlexaReply) => {
if (_.includes(this.config.defaultFulfillIntents, event.intent.name)) {
reply.fulfillIntent("YES");
_.each(event.intent.params, (value, slotName) => {
reply.fulfillSlot(slotName, "YES", "YES");
});
} else {
reply.fulfillIntent("NO");
}
return reply;
},
);
}
public getDirectiveHandlers() {
return [
AccountLinkingCard,
ConnectionsSendRequest,
DialogDelegate,
DialogElicitSlot,
DynamicEntitiesDirective,
GadgetControllerLightDirective,
GameEngineStartInputHandler,
GameEngineStopInputHandler,
Hint,
HomeCard,
PlayAudio,
VideoAppLaunch,
RenderTemplate,
APLTemplate,
APLCommand,
APLTTemplate,
APLTCommand,
StopAudio,
];
}
public getPlatformRequests() {
return AlexaRequests;
}
public async execute(
rawEvent: RequestEnvelope,
context?: AWSLambdaContext | AzureContext,
): Promise<any> {
this.checkAppIds(rawEvent);
const alexaEvent = (await this.getEvent(rawEvent, context)) as AlexaEvent;
const alexaReply = this.getReply(alexaEvent);
try {
this.app.onRequestStarted(this.checkSessionEndedRequest, true, "alexa");
return this.app.execute(alexaEvent, alexaReply);
} catch (error) {
return this.app.handleErrors(alexaEvent, error, alexaReply);
}
}
protected getReply(event: IVoxaEvent): IVoxaReply {
return new AlexaReply();
}
protected checkSessionEndedRequest(alexaEvent: AlexaEvent): void {
const { request } = alexaEvent.rawEvent;
if (request.type === "SessionEndedRequest" && request.reason === "ERROR") {
throw new OnSessionEndedError(request.error);
}
}
protected checkAppIds(rawEvent: RequestEnvelope): void {
if (!this.config.appIds) {
return;
}
// Validate that this AlexaRequest originated from authorized source.
const appId = rawEvent.context.System.application.applicationId;
const expectedAppids: string[] = _.isArray(this.config.appIds)
? this.config.appIds
: [this.config.appIds];
if (!_.includes(expectedAppids, appId)) {
throw new Error("Invalid applicationId");
}
}
}