1 | 'use strict';
|
2 | const _ = require('lodash');
|
3 | const debug = require('debug')('alexia:debug');
|
4 | const info = require('debug')('alexia:info');
|
5 | const parseError = require('./error-handler').parseError;
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | module.exports = (app, data, handlers, done) => {
|
15 | const appId = data.session.application.applicationId;
|
16 | const options = app.options;
|
17 |
|
18 |
|
19 | if (options && options.ids && options.ids.length > 0 && options.ids.indexOf(appId) === -1) {
|
20 | const e = parseError(new Error(`Application id: '${appId}' is not valid`));
|
21 | throw e;
|
22 | }
|
23 |
|
24 | if (data.session.new) {
|
25 | data.session.attributes = {
|
26 | previousIntent: '@start'
|
27 | };
|
28 | } else if (!data.session.attributes) {
|
29 | data.session.attributes = {};
|
30 | }
|
31 |
|
32 | const requestType = data.request.type;
|
33 |
|
34 | info(`Handling request: "${requestType}"`);
|
35 | debug(`Request payload: ${JSON.stringify(data, null, 2)}`);
|
36 | switch (requestType) {
|
37 |
|
38 | case 'LaunchRequest':
|
39 | callHandler(handlers.onStart, null, data.session.attributes, app, data, done);
|
40 | break;
|
41 |
|
42 | case 'IntentRequest':
|
43 | const intentName = data.request.intent.name;
|
44 | const intent = app.intents[data.request.intent.name];
|
45 |
|
46 | info(`Handling intent: "${intentName}"`);
|
47 | if (!intent) {
|
48 | const e = parseError(new Error(`Nonexistent intent: '${intentName}'`));
|
49 | throw e;
|
50 | }
|
51 |
|
52 | checkActionsAndHandle(intent, data.request.intent.slots, data.session.attributes, app, handlers, data, done);
|
53 | break;
|
54 |
|
55 | case 'SessionEndedRequest':
|
56 | callHandler(handlers.onEnd, null, data.session.attributes, app, data, done);
|
57 | break;
|
58 |
|
59 | default:
|
60 | const e = parseError(new Error(`Unsupported request: '${requestType}'`));
|
61 | throw e;
|
62 | }
|
63 |
|
64 | };
|
65 |
|
66 | const callHandler = (handler, slots, attrs, app, data, done) => {
|
67 |
|
68 |
|
69 | slots = _.transform(slots, (result, value) => {
|
70 | result[value.name] = value.value;
|
71 | }, {});
|
72 |
|
73 | const optionsReady = (options) => {
|
74 | done(createResponse(options, slots, attrs, app));
|
75 | };
|
76 |
|
77 |
|
78 | if (handler.length < 3) {
|
79 | optionsReady(handler(slots, attrs, data));
|
80 |
|
81 | } else {
|
82 | handler(slots, attrs, data, optionsReady);
|
83 | }
|
84 | };
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 | const checkActionsAndHandle = (intent, slots, attrs, app, handlers, data, done) => {
|
103 |
|
104 | if (app.actions.length === 0) {
|
105 |
|
106 | attrs.previousIntent = intent.name;
|
107 | callHandler(intent.handler, slots, attrs, app, data, done);
|
108 |
|
109 | } else {
|
110 |
|
111 | let action = _.find(app.actions, {from: attrs.previousIntent, to: intent.name});
|
112 |
|
113 |
|
114 | if (!action) {
|
115 | action = _.find(app.actions, {from: attrs.previousIntent, to: '*'});
|
116 | }
|
117 | if (!action) {
|
118 | action = _.find(app.actions, {from: '*', to: intent.name});
|
119 | }
|
120 |
|
121 | if (action) {
|
122 |
|
123 |
|
124 | if (action.if ? action.if(slots, attrs) : true) {
|
125 |
|
126 |
|
127 | attrs.previousIntent = intent.name;
|
128 | callHandler(intent.handler, slots, attrs, app, data, done);
|
129 |
|
130 | } else {
|
131 |
|
132 | if (action.fail) {
|
133 | callHandler(action.fail, slots, attrs, app, data, done);
|
134 | } else {
|
135 | callHandler(handlers.defaultActionFail, slots, attrs, app, data, done);
|
136 | }
|
137 | }
|
138 |
|
139 | } else {
|
140 | callHandler(handlers.defaultActionFail, slots, attrs, app, data, done);
|
141 | }
|
142 | }
|
143 | };
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 | const createCardObject = (card) => {
|
151 | if (card) {
|
152 |
|
153 | if (!card.type) {
|
154 | card.type = 'Simple';
|
155 | }
|
156 | return card;
|
157 | }
|
158 | };
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 | const getShouldEndSession = (options) => {
|
167 | if (!options || options.end === undefined) {
|
168 | return true;
|
169 | }
|
170 | return options.end;
|
171 | };
|
172 |
|
173 | const createResponse = (options, slots, attrs, app) => {
|
174 |
|
175 | if (typeof (options) === 'string') {
|
176 | options = {
|
177 | text: options
|
178 | };
|
179 | }
|
180 |
|
181 |
|
182 | const outputSpeech = createOutputSpeechObject(options.text, options.ssml);
|
183 |
|
184 | let sessionAttributes;
|
185 | if (options.attrs) {
|
186 |
|
187 | sessionAttributes = options.attrs;
|
188 | sessionAttributes.previousIntent = attrs.previousIntent;
|
189 |
|
190 | } else {
|
191 |
|
192 | sessionAttributes = attrs;
|
193 | }
|
194 |
|
195 | let responseObject = {
|
196 | version: app.options ? app.options.version : '0.0.1',
|
197 | sessionAttributes: sessionAttributes,
|
198 | response: {
|
199 | outputSpeech: outputSpeech,
|
200 | shouldEndSession: getShouldEndSession(options)
|
201 | }
|
202 | };
|
203 |
|
204 | if (options.reprompt) {
|
205 | responseObject.response.reprompt = {
|
206 | outputSpeech: createOutputSpeechObject(options.reprompt, options.ssml)
|
207 | };
|
208 |
|
209 | }
|
210 |
|
211 | let card = createCardObject(options.card);
|
212 | if (card) {
|
213 | responseObject.response.card = card;
|
214 | }
|
215 |
|
216 | return responseObject;
|
217 | };
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 | const createOutputSpeechObject = (text, ssml) => {
|
226 | let outputSpeech = {};
|
227 | if (!ssml) {
|
228 | outputSpeech.type = 'PlainText';
|
229 | outputSpeech.text = text;
|
230 | } else {
|
231 | outputSpeech.type = 'SSML';
|
232 | outputSpeech.ssml = text;
|
233 | }
|
234 | return outputSpeech;
|
235 | };
|