Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add general support for upcoming request types #313

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* [Display.ElementSelected](#display-element-selected)
* [AudioPlayer Event Request](#audioplayer-event-request)
* [PlaybackController Event Request](#playbackcontroller-event-request)
* [Other Event Request](#other-event-request)
* [Execute Code On Every Request](#execute-code-on-every-request)
* [pre()](#pre)
* [post()](#post)
Expand Down Expand Up @@ -228,6 +229,7 @@ response.audioPlayerStop()
response.audioPlayerClearQueue([ String clearBehavior ])

// tell Alexa whether the user's session is over; sessions end by default
// pass null or undefined to leave shouldEndSession undefined in the response, to satisfy newer API's
// you can optionally pass a reprompt message
response.shouldEndSession(boolean end [, String reprompt] )

Expand Down Expand Up @@ -546,6 +548,20 @@ app.playbackController('NextCommandIssued', (request, response) => {

Note that some device interactions don't always produce PlaybackController events. See the [PlaybackController Interface Introduction](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/custom-playbackcontroller-interface-reference#introduction) for more details.

### Other Event Request

Handle any new requests that don't have an explicit handler type available (such as new or pre-release features) using the general on() and passing the event type.

The following example will handle an imaginary request of type `DeviceEngine.InputHandler` if was added to the Alexa API.

```javascript
app.on('DeviceEngine.InputHandler', (request, response, request_json) => {
response.say("You triggered an event from device "+request_json.request.event.deviceName);
});
```

Note that the raw request json is sent as the 3rd parameter to make sure the handler function has access to all data in the case that the request format differs from other handler types.

## Execute Code On Every Request

In addition to specific event handlers, you can define functions that will run on every request.
Expand Down
16 changes: 14 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,12 @@ alexa.response = function(session) {
return this;
};
this.shouldEndSession = function(bool, reprompt) {
this.response.response.shouldEndSession = bool;
if (bool===null || typeof bool=="undefined") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicking, there should be some spaces around those ==.

delete this.response.response.shouldEndSession;
}
else {
this.response.response.shouldEndSession = bool;
}
if (reprompt) {
this.reprompt(reprompt);
}
Expand Down Expand Up @@ -457,7 +462,12 @@ alexa.app = function(name) {
}
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] = [];
Expand Down Expand Up @@ -598,6 +608,8 @@ alexa.app = function(name) {
} else {
throw "NO_DISPLAY_ELEMENT_SELECTED_FUNCTION";
}
} else if (typeof self.requestHandlers[requestType] === "function") {
return Promise.resolve(self.requestHandlers[requestType](request, response, request_json));
} else {
throw "INVALID_REQUEST_TYPE";
}
Expand Down
42 changes: 42 additions & 0 deletions test/fixtures/intent_request_on.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"version": "1.0",
"session": {
"new": true,
"sessionId": "amzn1.echo-api.session.abeee1a7-aee0-41e6-8192-e6faaed9f5ef",
"application": {
"applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
},
"attributes": {},
"user": {
"userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2"
}
},
"request": {
"type": "GameEngine.InputHandlerEvent",
"requestId": "amzn1.echo-api.request.9cdaa4db-f20e-4c58-8d01-c75322d6c423",
"timestamp": "2015-05-13T12:34:56Z",
"events": [
{
"name": "myEventName",
"inputEvents": [
{
"gadgetId": "someGadgetId1",
"timestamp": "2015-05-13T12:34:56Z",
"action": "down",
"color": "FF0000"
}
]
}
]
},
"context": {
"System": {
"user": {
"userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2"
},
"application": {
"applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
}
}
}
}
84 changes: 84 additions & 0 deletions test/test_alexa_app_on_request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*jshint expr: true*/
"use strict";
var chai = require("chai");
var chaiAsPromised = require("chai-as-promised");
var mockHelper = require("./helpers/mock_helper");
chai.use(chaiAsPromised);
var expect = chai.expect;
chai.config.includeStack = true;

import * as Alexa from '..';

describe("Alexa", function() {
describe("app", function() {
var testApp = new Alexa.app("testApp");

beforeEach(function() {
testApp = new Alexa.app("testApp");
});

describe("#request", function() {
describe("response", function() {
var mockRequest = mockHelper.load("intent_request_on.json");

context("with a request of type GameEngine.InputHandlerEvent", function() {
context("with no type handler", function() {
describe("outputSpeech", function() {
it("responds with INVALID_REQUEST_TYPE message", function() {
var subject = testApp.request(mockRequest).then(function(response) {
return response.response.outputSpeech;
});

return expect(subject).to.eventually.become({
ssml: "<speak>" + testApp.messages.INVALID_REQUEST_TYPE + "</speak>",
type: "SSML"
});
});
});
});

context("with a matching type handler", function() {
var expectedMessage="Valid Response";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another stuck =.

testApp.on('GameEngine.InputHandlerEvent',function(req,res) {
res.say(expectedMessage);
});
describe("outputSpeech", function() {
it("responds with expected message", function() {
testApp.on('GameEngine.InputHandlerEvent',function(req, res) {
res.say(expectedMessage);
});

var subject = testApp.request(mockRequest).then(function(response) {
return response.response.outputSpeech;
});

return expect(subject).to.eventually.become({
ssml: "<speak>" + expectedMessage + "</speak>",
type: "SSML"
});
});

it("responds with expected message for promise", function() {
testApp.on('GameEngine.InputHandlerEvent',function(req, res) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another missing space after ,.

return Promise.resolve().then(function() {
res.say(expectedMessage);
});
});

var subject = testApp.request(mockRequest).then(function(response) {
return response.response.outputSpeech;
});

return expect(subject).to.eventually.become({
ssml: "<speak>" + expectedMessage + "</speak>",
type: "SSML"
});
});

});
});
});
});
});
});
});
3 changes: 3 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ export class app {
playbackControllerEventHandlers: {[name: string]: PlaybackController};
playbackController: (eventName: string, func: RequestHandler) => void;

requestHandlers: {[name: string]: RequestHandler};
on: (handlerName: string, handler: RequestHandler) => void;

/** TODO: Figure out what the promise actually contains */
request: (requestJSON: alexa.Request) => Promise<alexa.ResponseBody>;

Expand Down