From 3ea1bbfe87f9bf11f2e412b55d73812770f651d3 Mon Sep 17 00:00:00 2001 From: Orly Frank Date: Tue, 18 Sep 2018 09:46:46 +0300 Subject: [PATCH 01/33] Update index.js add support for canFulfillIntent in request, response and register handler --- index.js | 112 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 93 insertions(+), 19 deletions(-) diff --git a/index.js b/index.js index 297d19a..abab8b1 100644 --- a/index.js +++ b/index.js @@ -48,7 +48,7 @@ alexa.response = function(session) { }; } 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); + this.response.response.reprompt.outputSpeech.ssml = SSML.fromStr(str, this.response.response.reprompt.outputSpeech.text); } return this; }; @@ -186,8 +186,66 @@ alexa.response = function(session) { this.sessionObject.clear(key); return this; }; + + this.canFulfillIntent= function(slots){ + this.canFulfillIntent = new alexa.canFulfillIntent(self.response.response, slots); + } + + this.canFulfill = function(canFulfill){ + if(!this.canFulfillIntent){ + this.canFulfillIntent = new alexa.canFulfillIntent(self.response.response); + } + this.canFulfillIntent.canFulfill(canFulfill); + } + + this.canFulfillSlot = function(slotName,canUnderstand, canFulfill){ + if(!this.canFulfillIntent){ + this.canFulfillIntent = new alexa.canFulfillIntent(self.response.response); + } + this.canFulfillIntent.canFulfillSlot(slotName,canUnderstand, canFulfill); + } + }; +alexa.canFulfillIntent = function (response, slots) { + //load alexa slots information from CanFulfillIntentRequest + //create default response + this.canFulfillIntent = { + "canFulfill": "NO", + "slots": {} + }; + response.canFulfillIntent = this.canFulfillIntent; + if (slots) { + for (let slotName in slots) { + let slotValue = { + "canUnderstand": "NO", + "canFulfill": "NO" + }; + this.canFulfillIntent.slots[slotName] = slotValue; + } + } + + this.canFulfill = function(canFulfill){ + this.canFulfillIntent.canFulfill = canFulfill; + } + + this.canFulfillSlot = function(slotName,canUnderstand, canFulfill){ + if(this.canFulfillIntent.slots[slotName]){ + let canFulfillSlot = this.canFulfillIntent.slots[slotName]; + canFulfillSlot.canUnderstand = canUnderstand; + canFulfillSlot.canFulfill = canFulfill; + } + else{ + this.canFulfillIntent.slots[slotName] = { + "canUnderstand": canUnderstand, + "canFulfill": canFulfill + } + } + + } + +} + alexa.directives = function(directives) { // load the alexa response directives information into details this.details = directives; @@ -196,11 +254,15 @@ alexa.directives = function(directives) { this.details.push(directive); }; + + this.clear = function() { this.details.length = 0; }; }; + + alexa.request = function(json) { this.data = json; this.slots = {}; @@ -283,6 +345,13 @@ alexa.request = function(json) { this.session = function(key) { return this.getSession().get(key); }; + + this.getCanFulfillIntent = function () { + if (!(this.data && this.data.request && this.data.request.intent)) { + return; + } + return this.data.request.intent; + } }; alexa.dialog = function(dialogState) { @@ -334,7 +403,7 @@ alexa.slot = function(slot) { return 'CONFIRMED' === this.confirmationStatus; }; this.resolution = function(idx) { - idx = ( typeof idx === 'number' && idx >= 0 && idx < this.resolutions.length ) ? idx : 0; + idx = idx && idx < this.resolutions.length ? idx : 0; return this.resolutions[idx]; }; }; @@ -480,22 +549,13 @@ alexa.app = function(name) { 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); - }); - }); + if (!value.id) { + value.id = null; + } + if (!value.synonyms) { + value.synonyms = []; } + valueObj = value; } self.customSlots[slotName].push(valueObj); }); @@ -527,6 +587,10 @@ alexa.app = function(name) { this.sessionEnded = function(func) { self.sessionEndedFunc = func; }; + this.canFulfillIntentFunc = null; + this.canFulfillIntent = function(func) { + self.canFulfillIntentFunc = func; + }; this.request = function(request_json) { var request = new alexa.request(request_json); var response = new alexa.response(request.getSession()); @@ -616,7 +680,17 @@ alexa.app = function(name) { } else { throw "NO_DISPLAY_ELEMENT_SELECTED_FUNCTION"; } - } else if (typeof self.requestHandlers[requestType] === "function") { + } else if ("CanFulfillIntentRequest" === requestType) { + if (typeof self.canFulfillIntentFunc === "function") { + let reqIntent = request.getCanFulfillIntent(); + if(reqIntent){ + response.canFulfillIntent(reqIntent.slots); + } + return Promise.resolve(self.canFulfillIntentFunc(request, response)); + } else { + throw "NO_CAN_FULFILL_FUNCTION"; + } + }else if (typeof self.requestHandlers[requestType] === "function") { return Promise.resolve(self.requestHandlers[requestType](request, response, request_json)); } else { throw "INVALID_REQUEST_TYPE"; @@ -708,7 +782,7 @@ alexa.app = function(name) { "id": value.id, "name": { "value": value.value, - "synonyms": value.synonyms + "synonyms": value.synonyms || [] } }; slotSchema.values.push(valueSchema); From 6865ed2e1a267d8a2fbbc12b0e8c8b897bcc8561 Mon Sep 17 00:00:00 2001 From: Orly Frank Date: Tue, 18 Sep 2018 09:53:32 +0300 Subject: [PATCH 02/33] Update index.js take head change --- index.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index abab8b1..464f296 100644 --- a/index.js +++ b/index.js @@ -48,7 +48,7 @@ alexa.response = function(session) { }; } 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.text); + this.response.response.reprompt.outputSpeech.ssml = SSML.fromStr(str, this.response.response.reprompt.outputSpeech.ssml); } return this; }; @@ -403,7 +403,7 @@ alexa.slot = function(slot) { return 'CONFIRMED' === this.confirmationStatus; }; this.resolution = function(idx) { - idx = idx && idx < this.resolutions.length ? idx : 0; + idx = ( typeof idx === 'number' && idx >= 0 && idx < this.resolutions.length ) ? idx : 0; return this.resolutions[idx]; }; }; @@ -549,13 +549,22 @@ alexa.app = function(name) { synonyms: [] }; } else { - if (!value.id) { - value.id = null; - } - if (!value.synonyms) { - value.synonyms = []; + 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); + }); + }); } - valueObj = value; } self.customSlots[slotName].push(valueObj); }); @@ -782,7 +791,7 @@ alexa.app = function(name) { "id": value.id, "name": { "value": value.value, - "synonyms": value.synonyms || [] + "synonyms": value.synonyms } }; slotSchema.values.push(valueSchema); From 1ddcd44d198f5fd08fe3169fce3b02ed9792fd8d Mon Sep 17 00:00:00 2001 From: Orly Frank Date: Sun, 23 Sep 2018 11:13:31 +0300 Subject: [PATCH 03/33] Update index.js Verify that your skill handles a CanFulfillIntentRequest call with no userId present --- index.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 464f296..f087353 100644 --- a/index.js +++ b/index.js @@ -313,7 +313,9 @@ alexa.request = function(json) { this.context = null; if (this.data.context) { - this.userId = this.data.context.System.user.userId; + if(this.data.context.System && this.data.context.System.user){ + this.userId = this.data.context.System.user.userId; + } this.applicationId = this.data.context.System.application.applicationId; this.context = this.data.context; } @@ -456,9 +458,13 @@ alexa.session = function(session) { // 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; + this.details.userId = null; + this.details.accessToken = null + if(this.details.user){ + this.details.userId = this.details.user.userId || null; + 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 From 5aebc3370b39dd86c23d5c8d78dabf1368ab19ba Mon Sep 17 00:00:00 2001 From: orly Date: Thu, 18 Oct 2018 13:54:12 +0300 Subject: [PATCH 04/33] add documentation add documentation --- index.js | 5 ++++- types/index.d.ts | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index f087353..95e19c6 100644 --- a/index.js +++ b/index.js @@ -189,6 +189,7 @@ alexa.response = function(session) { this.canFulfillIntent= function(slots){ this.canFulfillIntent = new alexa.canFulfillIntent(self.response.response, slots); + return this; } this.canFulfill = function(canFulfill){ @@ -196,13 +197,15 @@ alexa.response = function(session) { this.canFulfillIntent = new alexa.canFulfillIntent(self.response.response); } this.canFulfillIntent.canFulfill(canFulfill); + return this; } this.canFulfillSlot = function(slotName,canUnderstand, canFulfill){ if(!this.canFulfillIntent){ this.canFulfillIntent = new alexa.canFulfillIntent(self.response.response); } - this.canFulfillIntent.canFulfillSlot(slotName,canUnderstand, canFulfill); + this.canFulfillIntent.canFulfillSlot(slotName,canUnderstand, canFulfill); + return this; } }; diff --git a/types/index.d.ts b/types/index.d.ts index aad9ccc..8e34cf6 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -72,6 +72,9 @@ export class app { sessionEndedFunc?: RequestHandler; sessionEnded: (func: RequestHandler) => void; + canFulfillIntentFunc?: RequestHandler; + canFulfillIntent: (func: RequestHandler) => void; + displayElementSelectedFunc?: RequestHandler; displayElementSelected: (func: RequestHandler) => void; @@ -183,6 +186,11 @@ export class request { /** @deprecated */ session: (key: string) => any; + + /** Returns the request CanFulfillIntent */ + getCanFulfillIntent: () => object; + + } export class response { @@ -272,6 +280,12 @@ export class response { /** @deprecated */ clearSession: (key?: string) => response; + + canFulfillIntent: (slots?: any) => response; + + canFulfill: (canFulfill: string) => response; + + canFulfillSlot: (slotName: string,canUnderstand: string, canFulfill: string) => response; } // TODO: This is an Amazon-provided interface, but is more of a cluster of a half-dozen different interfaces with no documented parent interface. These are the methods/properties we're actually using. @@ -414,3 +428,10 @@ export interface PlaybackController { name: string; function: RequestHandler; } + +export class canFulfillIntent { + canFulfillIntent: object; + + canFulfill: (canFulfill: string) => void; + canFulfillSlot: (slotName: string ,canUnderstand: string , canFulfill: string) => void; +} From 107f00f1b87d28b400f1b6b73d84fec85b804865 Mon Sep 17 00:00:00 2001 From: orly Date: Thu, 18 Oct 2018 14:06:32 +0300 Subject: [PATCH 05/33] Update README.md --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 5e5f36c..a42011a 100644 --- a/README.md +++ b/README.md @@ -469,6 +469,19 @@ app.displayElementSelected(function(request, response) { }) ``` +### canFulfillIntent + +Define the handler for canFulfillIntent used for skill discovery +(https://developer.amazon.com/docs/custom-skills/implement-canfulfillintentrequest-for-name-free-interaction.html). + +```javascript +app.displayElementSelected(function(request, response) { + // The request object will return CanFulfillIntent object + handleRequestForCanFulfillIntent(request.getCanFulfillIntent()); +}) + +``` + ### SessionEndRequest ```javascript From ed81661a806c374b041204855163351455259dee Mon Sep 17 00:00:00 2001 From: orly Date: Thu, 18 Oct 2018 14:09:04 +0300 Subject: [PATCH 06/33] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a42011a..9821c21 100644 --- a/README.md +++ b/README.md @@ -471,7 +471,7 @@ app.displayElementSelected(function(request, response) { ### canFulfillIntent -Define the handler for canFulfillIntent used for skill discovery +Define the handler for canFulfillIntent used for skill discovery [Implementation instructions] (https://developer.amazon.com/docs/custom-skills/implement-canfulfillintentrequest-for-name-free-interaction.html). ```javascript From edcc431845dbc5e11715820aee938f80abb3df53 Mon Sep 17 00:00:00 2001 From: orly Date: Thu, 18 Oct 2018 14:23:57 +0300 Subject: [PATCH 07/33] Update README.md --- README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9821c21..16e9d6d 100644 --- a/README.md +++ b/README.md @@ -471,15 +471,24 @@ app.displayElementSelected(function(request, response) { ### canFulfillIntent -Define the handler for canFulfillIntent used for skill discovery [Implementation instructions] -(https://developer.amazon.com/docs/custom-skills/implement-canfulfillintentrequest-for-name-free-interaction.html). +Define the handler for canFulfillIntent used for skill discovery. For instance the [Implementation instructions](https://developer.amazon.com/docs/custom-skills/implement-canfulfillintentrequest-for-name-free-interaction.html). + +See detailed explanation on the Intent and Slot [logic](https://developer.amazon.com/docs/custom-skills/understand-name-free-interaction-for-custom-skills.html). + ```javascript app.displayElementSelected(function(request, response) { // The request object will return CanFulfillIntent object - handleRequestForCanFulfillIntent(request.getCanFulfillIntent()); -}) - + let canFulfillIntent = request.getCanFulfillIntent(); + + //by default the response will include No for the intent and slots + //add your logic to determine if you can fulfill the intent and each slot + + //in this example the response is Yes for the intent and for both slots + response.canFulfill("YES"); + response.canFulfillSlot("Sound","YES","YES"); + response.canFulfillSlot("Another","YES","YES"); +}); ``` ### SessionEndRequest From 13a462a759cc353f7cafa522927b47d61ce3d0f4 Mon Sep 17 00:00:00 2001 From: orly Date: Thu, 18 Oct 2018 14:30:39 +0300 Subject: [PATCH 08/33] Update README.md --- README.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 16e9d6d..8c7d14d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ * [Display.ElementSelected](#display-element-selected) * [AudioPlayer Event Request](#audioplayer-event-request) * [PlaybackController Event Request](#playbackcontroller-event-request) + * [CanFulfillIntent Request](#playbackcontroller-event-request) * [Other Event Request](#other-event-request) * [Execute Code On Every Request](#execute-code-on-every-request) * [pre()](#pre) @@ -468,29 +469,6 @@ app.displayElementSelected(function(request, response) { handleRequestForTouchEvent(request.selectedElementToken) }) ``` - -### canFulfillIntent - -Define the handler for canFulfillIntent used for skill discovery. For instance the [Implementation instructions](https://developer.amazon.com/docs/custom-skills/implement-canfulfillintentrequest-for-name-free-interaction.html). - -See detailed explanation on the Intent and Slot [logic](https://developer.amazon.com/docs/custom-skills/understand-name-free-interaction-for-custom-skills.html). - - -```javascript -app.displayElementSelected(function(request, response) { - // The request object will return CanFulfillIntent object - let canFulfillIntent = request.getCanFulfillIntent(); - - //by default the response will include No for the intent and slots - //add your logic to determine if you can fulfill the intent and each slot - - //in this example the response is Yes for the intent and for both slots - response.canFulfill("YES"); - response.canFulfillSlot("Sound","YES","YES"); - response.canFulfillSlot("Another","YES","YES"); -}); -``` - ### SessionEndRequest ```javascript @@ -577,6 +555,28 @@ 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. +### CanFulfillIntent + +Define the handler for canFulfillIntent used for skill discovery. For instance the [Implementation instructions](https://developer.amazon.com/docs/custom-skills/implement-canfulfillintentrequest-for-name-free-interaction.html). + +See detailed explanation on the Intent and Slot [logic](https://developer.amazon.com/docs/custom-skills/understand-name-free-interaction-for-custom-skills.html). + + +```javascript +app.displayElementSelected(function(request, response) { + // The request object will return CanFulfillIntent object + let canFulfillIntent = request.getCanFulfillIntent(); + + //by default the response will include No for the intent and slots + //add your logic to determine if you can fulfill the intent and each slot + + //in this example the response is Yes for the intent and for both slots + response.canFulfill("YES"); + response.canFulfillSlot("Sound","YES","YES"); + response.canFulfillSlot("Another","YES","YES"); +}); +``` + ### 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. From 8132415b5ce19e227202fd863538c3dbcd0c8381 Mon Sep 17 00:00:00 2001 From: orly Date: Thu, 18 Oct 2018 14:31:24 +0300 Subject: [PATCH 09/33] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c7d14d..57a6e32 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ * [Display.ElementSelected](#display-element-selected) * [AudioPlayer Event Request](#audioplayer-event-request) * [PlaybackController Event Request](#playbackcontroller-event-request) - * [CanFulfillIntent Request](#playbackcontroller-event-request) + * [CanFulfillIntent Request](#CanFulfillIntent) * [Other Event Request](#other-event-request) * [Execute Code On Every Request](#execute-code-on-every-request) * [pre()](#pre) From 94176325484d8ae6a6dcda4369ac84dcf2a01459 Mon Sep 17 00:00:00 2001 From: orly Date: Thu, 18 Oct 2018 14:32:22 +0300 Subject: [PATCH 10/33] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 57a6e32..8d66a1b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ * [Display.ElementSelected](#display-element-selected) * [AudioPlayer Event Request](#audioplayer-event-request) * [PlaybackController Event Request](#playbackcontroller-event-request) - * [CanFulfillIntent Request](#CanFulfillIntent) + * [CanFulfillIntent Request](#canfulfillintent) * [Other Event Request](#other-event-request) * [Execute Code On Every Request](#execute-code-on-every-request) * [pre()](#pre) From c88e2ddfc66f66d3acf28b95982d97c3ca949224 Mon Sep 17 00:00:00 2001 From: orly Date: Sun, 21 Oct 2018 09:13:47 +0300 Subject: [PATCH 11/33] add missinf semicolon --- index.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 95e19c6..9544296 100644 --- a/index.js +++ b/index.js @@ -190,7 +190,7 @@ alexa.response = function(session) { this.canFulfillIntent= function(slots){ this.canFulfillIntent = new alexa.canFulfillIntent(self.response.response, slots); return this; - } + }; this.canFulfill = function(canFulfill){ if(!this.canFulfillIntent){ @@ -198,7 +198,7 @@ alexa.response = function(session) { } this.canFulfillIntent.canFulfill(canFulfill); return this; - } + }; this.canFulfillSlot = function(slotName,canUnderstand, canFulfill){ if(!this.canFulfillIntent){ @@ -206,8 +206,7 @@ alexa.response = function(session) { } this.canFulfillIntent.canFulfillSlot(slotName,canUnderstand, canFulfill); return this; - } - + }; }; alexa.canFulfillIntent = function (response, slots) { @@ -217,6 +216,7 @@ alexa.canFulfillIntent = function (response, slots) { "canFulfill": "NO", "slots": {} }; + response.canFulfillIntent = this.canFulfillIntent; if (slots) { for (let slotName in slots) { @@ -226,11 +226,11 @@ alexa.canFulfillIntent = function (response, slots) { }; this.canFulfillIntent.slots[slotName] = slotValue; } - } + }; this.canFulfill = function(canFulfill){ this.canFulfillIntent.canFulfill = canFulfill; - } + }; this.canFulfillSlot = function(slotName,canUnderstand, canFulfill){ if(this.canFulfillIntent.slots[slotName]){ @@ -243,11 +243,9 @@ alexa.canFulfillIntent = function (response, slots) { "canUnderstand": canUnderstand, "canFulfill": canFulfill } - } - - } - -} + } + }; +}; alexa.directives = function(directives) { // load the alexa response directives information into details @@ -356,7 +354,7 @@ alexa.request = function(json) { return; } return this.data.request.intent; - } + }; }; alexa.dialog = function(dialogState) { @@ -462,7 +460,7 @@ alexa.session = function(session) { this.details = session; // @deprecated this.details.userId = null; - this.details.accessToken = null + this.details.accessToken = null; if(this.details.user){ this.details.userId = this.details.user.userId || null; this.details.accessToken = this.details.user.accessToken || null; From 901f077e37da0eb196d47287fe284b62cc98b330 Mon Sep 17 00:00:00 2001 From: orly Date: Sun, 21 Oct 2018 09:35:17 +0300 Subject: [PATCH 12/33] add missing whitespace --- types/index.d.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index 8e34cf6..1609114 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -189,8 +189,6 @@ export class request { /** Returns the request CanFulfillIntent */ getCanFulfillIntent: () => object; - - } export class response { @@ -285,7 +283,7 @@ export class response { canFulfill: (canFulfill: string) => response; - canFulfillSlot: (slotName: string,canUnderstand: string, canFulfill: string) => response; + canFulfillSlot: (slotName: string, canUnderstand: string, canFulfill: string) => response; } // TODO: This is an Amazon-provided interface, but is more of a cluster of a half-dozen different interfaces with no documented parent interface. These are the methods/properties we're actually using. @@ -433,5 +431,5 @@ export class canFulfillIntent { canFulfillIntent: object; canFulfill: (canFulfill: string) => void; - canFulfillSlot: (slotName: string ,canUnderstand: string , canFulfill: string) => void; -} + canFulfillSlot: (slotName: string , canUnderstand: string , canFulfill: string) => void; +} \ No newline at end of file From 29da17916ccc327c1eb7cbec2f1228846b55057e Mon Sep 17 00:00:00 2001 From: orly Date: Sun, 21 Oct 2018 10:03:30 +0300 Subject: [PATCH 13/33] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca26ad9..4696f25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### 4.2.3 (Next) +* [#375](https://github.com/alexa-js/alexa-app/pull/375): Implement CanFulfillIntent requests - [@orlylev](https://github.com/orlylev). * [#344](https://github.com/alexa-js/alexa-app/pull/344): Add coveralls as a check for pull requests - [@kobim](https://github.com/kobim). * [#352](https://github.com/alexa-js/alexa-app/pull/352): Allow utterance expansion in custom slot type synonyms - [@daanzu](https://github.com/daanzu). * [#361](https://github.com/alexa-js/alexa-app/pull/361): Fix object reference for dialogState in in doc - [@Sephtenen](https://github.com/Sephtenen). From a5ba4d6f6e8dfde9847c5c4814083ff9e657c9fb Mon Sep 17 00:00:00 2001 From: orly Date: Sun, 21 Oct 2018 10:14:08 +0300 Subject: [PATCH 14/33] fix travis issues --- index.js | 4 ++-- types/index.d.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 9544296..1c5cc4b 100644 --- a/index.js +++ b/index.js @@ -226,7 +226,7 @@ alexa.canFulfillIntent = function (response, slots) { }; this.canFulfillIntent.slots[slotName] = slotValue; } - }; + } this.canFulfill = function(canFulfill){ this.canFulfillIntent.canFulfill = canFulfill; @@ -242,7 +242,7 @@ alexa.canFulfillIntent = function (response, slots) { this.canFulfillIntent.slots[slotName] = { "canUnderstand": canUnderstand, "canFulfill": canFulfill - } + }; } }; }; diff --git a/types/index.d.ts b/types/index.d.ts index 1609114..7d1dd13 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -432,4 +432,4 @@ export class canFulfillIntent { canFulfill: (canFulfill: string) => void; canFulfillSlot: (slotName: string , canUnderstand: string , canFulfill: string) => void; -} \ No newline at end of file +} From 62bbdff0aa13869b4db4d3f0d11c1d58bb20d6ec Mon Sep 17 00:00:00 2001 From: orly Date: Sun, 21 Oct 2018 10:58:28 +0300 Subject: [PATCH 15/33] move contribution line to the end --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4696f25..21b99fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,11 @@ ### 4.2.3 (Next) -* [#375](https://github.com/alexa-js/alexa-app/pull/375): Implement CanFulfillIntent requests - [@orlylev](https://github.com/orlylev). * [#344](https://github.com/alexa-js/alexa-app/pull/344): Add coveralls as a check for pull requests - [@kobim](https://github.com/kobim). * [#352](https://github.com/alexa-js/alexa-app/pull/352): Allow utterance expansion in custom slot type synonyms - [@daanzu](https://github.com/daanzu). * [#361](https://github.com/alexa-js/alexa-app/pull/361): Fix object reference for dialogState in in doc - [@Sephtenen](https://github.com/Sephtenen). * [#364](https://github.com/alexa-js/alexa-app/pull/364): Fix reprompt() to concatenate multiple SSML prompts - [@andrewjhunt](https://github.com/andrewjhunt). +* [#375](https://github.com/alexa-js/alexa-app/pull/375): Implement CanFulfillIntent requests - [@orlylev](https://github.com/orlylev). * Your contribution here. ### 4.2.2 (April 7, 2018) From de85028c0161e07fde6c377ca808166ebd1b558f Mon Sep 17 00:00:00 2001 From: Orly Frank Date: Sun, 21 Oct 2018 12:01:00 +0300 Subject: [PATCH 16/33] Add again after force rebase --- index.js | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 95 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 26b7fb1..1c5cc4b 100644 --- a/index.js +++ b/index.js @@ -186,6 +186,65 @@ alexa.response = function(session) { this.sessionObject.clear(key); return this; }; + + this.canFulfillIntent= function(slots){ + this.canFulfillIntent = new alexa.canFulfillIntent(self.response.response, slots); + return this; + }; + + this.canFulfill = function(canFulfill){ + if(!this.canFulfillIntent){ + this.canFulfillIntent = new alexa.canFulfillIntent(self.response.response); + } + this.canFulfillIntent.canFulfill(canFulfill); + return this; + }; + + this.canFulfillSlot = function(slotName,canUnderstand, canFulfill){ + if(!this.canFulfillIntent){ + this.canFulfillIntent = new alexa.canFulfillIntent(self.response.response); + } + this.canFulfillIntent.canFulfillSlot(slotName,canUnderstand, canFulfill); + return this; + }; +}; + +alexa.canFulfillIntent = function (response, slots) { + //load alexa slots information from CanFulfillIntentRequest + //create default response + this.canFulfillIntent = { + "canFulfill": "NO", + "slots": {} + }; + + response.canFulfillIntent = this.canFulfillIntent; + if (slots) { + for (let slotName in slots) { + let slotValue = { + "canUnderstand": "NO", + "canFulfill": "NO" + }; + this.canFulfillIntent.slots[slotName] = slotValue; + } + } + + this.canFulfill = function(canFulfill){ + this.canFulfillIntent.canFulfill = canFulfill; + }; + + this.canFulfillSlot = function(slotName,canUnderstand, canFulfill){ + if(this.canFulfillIntent.slots[slotName]){ + let canFulfillSlot = this.canFulfillIntent.slots[slotName]; + canFulfillSlot.canUnderstand = canUnderstand; + canFulfillSlot.canFulfill = canFulfill; + } + else{ + this.canFulfillIntent.slots[slotName] = { + "canUnderstand": canUnderstand, + "canFulfill": canFulfill + }; + } + }; }; alexa.directives = function(directives) { @@ -196,11 +255,15 @@ alexa.directives = function(directives) { this.details.push(directive); }; + + this.clear = function() { this.details.length = 0; }; }; + + alexa.request = function(json) { this.data = json; this.slots = {}; @@ -251,7 +314,9 @@ alexa.request = function(json) { this.context = null; if (this.data.context) { - this.userId = this.data.context.System.user.userId; + if(this.data.context.System && this.data.context.System.user){ + this.userId = this.data.context.System.user.userId; + } this.applicationId = this.data.context.System.application.applicationId; this.context = this.data.context; } @@ -283,6 +348,13 @@ alexa.request = function(json) { this.session = function(key) { return this.getSession().get(key); }; + + this.getCanFulfillIntent = function () { + if (!(this.data && this.data.request && this.data.request.intent)) { + return; + } + return this.data.request.intent; + }; }; alexa.dialog = function(dialogState) { @@ -387,9 +459,13 @@ alexa.session = function(session) { // 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; + this.details.userId = null; + this.details.accessToken = null; + if(this.details.user){ + this.details.userId = this.details.user.userId || null; + 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 @@ -527,6 +603,10 @@ alexa.app = function(name) { this.sessionEnded = function(func) { self.sessionEndedFunc = func; }; + this.canFulfillIntentFunc = null; + this.canFulfillIntent = function(func) { + self.canFulfillIntentFunc = func; + }; this.request = function(request_json) { var request = new alexa.request(request_json); var response = new alexa.response(request.getSession()); @@ -543,7 +623,6 @@ alexa.app = function(name) { postPromise = Promise.resolve(self.post(request, response, requestType, exception)); } return postPromise.then(function() { - response.prepare(); if (!response.resolved) { response.resolved = true; } @@ -558,7 +637,6 @@ alexa.app = function(name) { postPromise = Promise.resolve(self.post(request, response, requestType, exception)); } return postPromise.then(function() { - response.prepare(); if (!response.resolved) { response.resolved = true; throw msg; @@ -618,7 +696,17 @@ alexa.app = function(name) { } else { throw "NO_DISPLAY_ELEMENT_SELECTED_FUNCTION"; } - } else if (typeof self.requestHandlers[requestType] === "function") { + } else if ("CanFulfillIntentRequest" === requestType) { + if (typeof self.canFulfillIntentFunc === "function") { + let reqIntent = request.getCanFulfillIntent(); + if(reqIntent){ + response.canFulfillIntent(reqIntent.slots); + } + return Promise.resolve(self.canFulfillIntentFunc(request, response)); + } else { + throw "NO_CAN_FULFILL_FUNCTION"; + } + }else if (typeof self.requestHandlers[requestType] === "function") { return Promise.resolve(self.requestHandlers[requestType](request, response, request_json)); } else { throw "INVALID_REQUEST_TYPE"; From 77d81d6f478b0cfe3477cd2ad0ec0256941c0443 Mon Sep 17 00:00:00 2001 From: Orly Frank Date: Sun, 21 Oct 2018 12:01:47 +0300 Subject: [PATCH 17/33] Commit again after force rebase --- types/index.d.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/types/index.d.ts b/types/index.d.ts index aad9ccc..7d1dd13 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -72,6 +72,9 @@ export class app { sessionEndedFunc?: RequestHandler; sessionEnded: (func: RequestHandler) => void; + canFulfillIntentFunc?: RequestHandler; + canFulfillIntent: (func: RequestHandler) => void; + displayElementSelectedFunc?: RequestHandler; displayElementSelected: (func: RequestHandler) => void; @@ -183,6 +186,9 @@ export class request { /** @deprecated */ session: (key: string) => any; + + /** Returns the request CanFulfillIntent */ + getCanFulfillIntent: () => object; } export class response { @@ -272,6 +278,12 @@ export class response { /** @deprecated */ clearSession: (key?: string) => response; + + canFulfillIntent: (slots?: any) => response; + + canFulfill: (canFulfill: string) => response; + + canFulfillSlot: (slotName: string, canUnderstand: string, canFulfill: string) => response; } // TODO: This is an Amazon-provided interface, but is more of a cluster of a half-dozen different interfaces with no documented parent interface. These are the methods/properties we're actually using. @@ -414,3 +426,10 @@ export interface PlaybackController { name: string; function: RequestHandler; } + +export class canFulfillIntent { + canFulfillIntent: object; + + canFulfill: (canFulfill: string) => void; + canFulfillSlot: (slotName: string , canUnderstand: string , canFulfill: string) => void; +} From 7051bec21d7064a9380074735d7bb7e22b621f8c Mon Sep 17 00:00:00 2001 From: Orly Frank Date: Sun, 21 Oct 2018 12:02:39 +0300 Subject: [PATCH 18/33] Commit again after force rebase --- README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e5f36c..8d66a1b 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ * [Display.ElementSelected](#display-element-selected) * [AudioPlayer Event Request](#audioplayer-event-request) * [PlaybackController Event Request](#playbackcontroller-event-request) + * [CanFulfillIntent Request](#canfulfillintent) * [Other Event Request](#other-event-request) * [Execute Code On Every Request](#execute-code-on-every-request) * [pre()](#pre) @@ -468,7 +469,6 @@ app.displayElementSelected(function(request, response) { handleRequestForTouchEvent(request.selectedElementToken) }) ``` - ### SessionEndRequest ```javascript @@ -555,6 +555,28 @@ 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. +### CanFulfillIntent + +Define the handler for canFulfillIntent used for skill discovery. For instance the [Implementation instructions](https://developer.amazon.com/docs/custom-skills/implement-canfulfillintentrequest-for-name-free-interaction.html). + +See detailed explanation on the Intent and Slot [logic](https://developer.amazon.com/docs/custom-skills/understand-name-free-interaction-for-custom-skills.html). + + +```javascript +app.displayElementSelected(function(request, response) { + // The request object will return CanFulfillIntent object + let canFulfillIntent = request.getCanFulfillIntent(); + + //by default the response will include No for the intent and slots + //add your logic to determine if you can fulfill the intent and each slot + + //in this example the response is Yes for the intent and for both slots + response.canFulfill("YES"); + response.canFulfillSlot("Sound","YES","YES"); + response.canFulfillSlot("Another","YES","YES"); +}); +``` + ### 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. From 9d3949958a919eeb19cd34a58b845dd9105f8c53 Mon Sep 17 00:00:00 2001 From: orly Date: Sun, 21 Oct 2018 14:05:33 +0300 Subject: [PATCH 19/33] add unut testing CanFulfillIntent --- test/test_alexa_app_fulfill_intent_request.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/test_alexa_app_fulfill_intent_request.js diff --git a/test/test_alexa_app_fulfill_intent_request.js b/test/test_alexa_app_fulfill_intent_request.js new file mode 100644 index 0000000..baf67ac --- /dev/null +++ b/test/test_alexa_app_fulfill_intent_request.js @@ -0,0 +1,35 @@ +/*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("#CanFulfillIntent_request", function () { + describe("response", function () { + var mockRequest = mockHelper.load("can_fulfill_intent_request_play_sound.json"); + var request = testApp.request(mockRequest); + + it("no handler responds with a exception", function() { + + var subject = request.then(function(response) { + return response.response; + }); + return expect(subject).to.eventually.be.rejectedWith("NO_CAN_FULFILL_FUNCTION"); + }); + }); + }); + }); +}); From e69adf7ad7137eeea34bd0890af298c8dc6dd013 Mon Sep 17 00:00:00 2001 From: orly Date: Sun, 21 Oct 2018 14:28:21 +0300 Subject: [PATCH 20/33] Update test/test_alexa_app_fulfill_intent_request.js --- test/test_alexa_app_fulfill_intent_request.js | 75 +++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/test/test_alexa_app_fulfill_intent_request.js b/test/test_alexa_app_fulfill_intent_request.js index baf67ac..5bdc702 100644 --- a/test/test_alexa_app_fulfill_intent_request.js +++ b/test/test_alexa_app_fulfill_intent_request.js @@ -18,17 +18,82 @@ describe("Alexa", function () { }); describe("#CanFulfillIntent_request", function () { + describe("response", function () { var mockRequest = mockHelper.load("can_fulfill_intent_request_play_sound.json"); + var request = testApp.request(mockRequest); + beforeEach(function () { + request = testApp.request(mockRequest); + }); - it("no handler responds with a exception", function() { - - var subject = request.then(function(response) { - return response.response; + it("no handler responds with a exception", function () { + var subject = request.then(function (response) { + return response.response; }); return expect(subject).to.eventually.be.rejectedWith("NO_CAN_FULFILL_FUNCTION"); - }); + }); + + it("empty handler responds with default NO for canFulfill", function () { + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(function (req, res) { + }); + + var subject = request.then(function (response) { + return response.response.canFulfillIntent.canFulfill; + }); + return expect(subject).to.eventually.become('NO'); + }); + + it("empty handler responds with default NO for slot Sound", function () { + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(function (req, res) { + }); + + var subject = request.then(function (response) { + return response.response.canFulfillIntent.slots; + }); + return expect(subject).to.eventually.become({ + "Sound": { + "canUnderstand": "NO", + "canFulfill": "NO" + } + }); + }); + + it("custom handler responds with default YES for canFulfill", function () { + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(function (req, res) { + res.canFulfill("YES"); + }); + + var subject = request.then(function (response) { + return response.response.canFulfillIntent.canFulfill; + }); + return expect(subject).to.eventually.become('YES'); + }); + + it("custom handler responds with default YES for slot Sound", function () { + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(function (req, res) { + res.canFulfillSlot("Sound","YES","YES"); + }); + + var subject = request.then(function (response) { + return response.response.canFulfillIntent.slots; + }); + return expect(subject).to.eventually.become({ + "Sound": { + "canUnderstand": "YES", + "canFulfill": "YES" + } + }); + }); + }); }); }); From 21136107a371b50d269a45a8ed647379374bc699 Mon Sep 17 00:00:00 2001 From: orly Date: Sun, 21 Oct 2018 14:43:34 +0300 Subject: [PATCH 21/33] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21b99fb..44668ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ * [#352](https://github.com/alexa-js/alexa-app/pull/352): Allow utterance expansion in custom slot type synonyms - [@daanzu](https://github.com/daanzu). * [#361](https://github.com/alexa-js/alexa-app/pull/361): Fix object reference for dialogState in in doc - [@Sephtenen](https://github.com/Sephtenen). * [#364](https://github.com/alexa-js/alexa-app/pull/364): Fix reprompt() to concatenate multiple SSML prompts - [@andrewjhunt](https://github.com/andrewjhunt). -* [#375](https://github.com/alexa-js/alexa-app/pull/375): Implement CanFulfillIntent requests - [@orlylev](https://github.com/orlylev). +* [#377](https://github.com/alexa-js/alexa-app/pull/375): Implement CanFulfillIntent requests - [@orlylev](https://github.com/orlylev). * Your contribution here. ### 4.2.2 (April 7, 2018) From 5ea6a018b2979d64399c49bc211dc8dd3a8e3fd8 Mon Sep 17 00:00:00 2001 From: orly Date: Sun, 21 Oct 2018 15:22:13 +0300 Subject: [PATCH 22/33] Update test/test_alexa_app_fulfill_intent_request.js --- test/test_alexa_app_fulfill_intent_request.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/test_alexa_app_fulfill_intent_request.js b/test/test_alexa_app_fulfill_intent_request.js index 5bdc702..a3ae2fa 100644 --- a/test/test_alexa_app_fulfill_intent_request.js +++ b/test/test_alexa_app_fulfill_intent_request.js @@ -19,6 +19,23 @@ describe("Alexa", function () { describe("#CanFulfillIntent_request", function () { + describe("request", function () { + var mockRequest = mockHelper.load("can_fulfill_intent_request_play_sound.json"); + + var request = testApp.request(mockRequest); + beforeEach(function () { + request = testApp.request(mockRequest); + }); + + it("valid request getCanFulfillIntent return intent name PlaySound", function () { + var subject = request.getCanFulfillIntent().name; + + //return expect(subject).to.equal('PlaySound'); + + }); + + }); + describe("response", function () { var mockRequest = mockHelper.load("can_fulfill_intent_request_play_sound.json"); From 345c7682caf619f4babed2ff890630e3f3ba9f19 Mon Sep 17 00:00:00 2001 From: orly Date: Mon, 22 Oct 2018 10:34:36 +0300 Subject: [PATCH 23/33] fix getCanFulfillIntent text --- test/test_alexa_app_fulfill_intent_request.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/test_alexa_app_fulfill_intent_request.js b/test/test_alexa_app_fulfill_intent_request.js index a3ae2fa..cf0d4f4 100644 --- a/test/test_alexa_app_fulfill_intent_request.js +++ b/test/test_alexa_app_fulfill_intent_request.js @@ -28,9 +28,18 @@ describe("Alexa", function () { }); it("valid request getCanFulfillIntent return intent name PlaySound", function () { - var subject = request.getCanFulfillIntent().name; - //return expect(subject).to.equal('PlaySound'); + var handler = function (req, res){ + handler.result = req.getCanFulfillIntent().name; + }; + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(handler); + + var subject = request.then(function (response) { + return handler.result; + }); + return expect(subject).to.eventually.become('PlaySound'); }); From efb97733de9fdbea73584b604f94b9998fb420eb Mon Sep 17 00:00:00 2001 From: orly Date: Mon, 22 Oct 2018 10:40:16 +0300 Subject: [PATCH 24/33] add getCanFulfillIntent slot test --- test/test_alexa_app_fulfill_intent_request.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/test_alexa_app_fulfill_intent_request.js b/test/test_alexa_app_fulfill_intent_request.js index cf0d4f4..4f87900 100644 --- a/test/test_alexa_app_fulfill_intent_request.js +++ b/test/test_alexa_app_fulfill_intent_request.js @@ -43,6 +43,23 @@ describe("Alexa", function () { }); + it("valid request getCanFulfillIntent return Sound slot", function () { + + var handler = function (req, res){ + handler.result = req.getCanFulfillIntent().slots; + }; + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(handler); + + var subject = request.then(function (response) { + return handler.result.Sound; + }); + return expect(subject).to.eventually.become({ + "name": "Sound", + "value": "crickets" + }); + }); }); describe("response", function () { From efaf53136129c08b67686d84cc63573c314f322c Mon Sep 17 00:00:00 2001 From: orly Date: Mon, 22 Oct 2018 13:23:34 +0300 Subject: [PATCH 25/33] add slot test --- test/test_alexa_app_fulfill_intent_request.js | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/test/test_alexa_app_fulfill_intent_request.js b/test/test_alexa_app_fulfill_intent_request.js index 4f87900..3b54667 100644 --- a/test/test_alexa_app_fulfill_intent_request.js +++ b/test/test_alexa_app_fulfill_intent_request.js @@ -119,7 +119,7 @@ describe("Alexa", function () { return expect(subject).to.eventually.become('YES'); }); - it("custom handler responds with default YES for slot Sound", function () { + it("custom handler responds with YES for slot Sound", function () { testApp.pre = undefined; testApp.post = undefined; testApp.canFulfillIntent(function (req, res) { @@ -137,6 +137,24 @@ describe("Alexa", function () { }); }); + it("custom handler responds with canUnderstand YES canFulfill No for slot Sound", function () { + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(function (req, res) { + res.canFulfillSlot("Sound","YES","NO"); + }); + + var subject = request.then(function (response) { + return response.response.canFulfillIntent.slots; + }); + return expect(subject).to.eventually.become({ + "Sound": { + "canUnderstand": "YES", + "canFulfill": "NO" + } + }); + }); + }); }); }); From 35b9a69cf4693e10f8bdeaf090abc822b8d8a06e Mon Sep 17 00:00:00 2001 From: orly Date: Tue, 23 Oct 2018 09:11:16 +0300 Subject: [PATCH 26/33] Updated PR number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44668ed..350d023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ * [#352](https://github.com/alexa-js/alexa-app/pull/352): Allow utterance expansion in custom slot type synonyms - [@daanzu](https://github.com/daanzu). * [#361](https://github.com/alexa-js/alexa-app/pull/361): Fix object reference for dialogState in in doc - [@Sephtenen](https://github.com/Sephtenen). * [#364](https://github.com/alexa-js/alexa-app/pull/364): Fix reprompt() to concatenate multiple SSML prompts - [@andrewjhunt](https://github.com/andrewjhunt). -* [#377](https://github.com/alexa-js/alexa-app/pull/375): Implement CanFulfillIntent requests - [@orlylev](https://github.com/orlylev). +* [#378](https://github.com/alexa-js/alexa-app/pull/375): Implement CanFulfillIntent requests - [@orlylev](https://github.com/orlylev). * Your contribution here. ### 4.2.2 (April 7, 2018) From f17440b9031cd450499858e3971521d25514d96c Mon Sep 17 00:00:00 2001 From: orly Date: Tue, 23 Oct 2018 09:11:27 +0300 Subject: [PATCH 27/33] add tests --- test/test_alexa_app_fulfill_intent_request.js | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/test_alexa_app_fulfill_intent_request.js b/test/test_alexa_app_fulfill_intent_request.js index 3b54667..fe992bd 100644 --- a/test/test_alexa_app_fulfill_intent_request.js +++ b/test/test_alexa_app_fulfill_intent_request.js @@ -155,6 +155,46 @@ describe("Alexa", function () { }); }); + it("custom handler responds with canUnderstand No canFulfill Yes for slot Sound", function () { + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(function (req, res) { + res.canFulfillSlot("Sound","NO","YES"); + }); + + var subject = request.then(function (response) { + return response.response.canFulfillIntent.slots; + }); + return expect(subject).to.eventually.become({ + "Sound": { + "canUnderstand": "NO", + "canFulfill": "YES" + } + }); + }); + + it("custom handler responds with YES for new slot Play", function () { + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(function (req, res) { + res.canFulfillSlot("Play","YES","YES"); + }); + + var subject = request.then(function (response) { + return response.response.canFulfillIntent.slots; + }); + return expect(subject).to.eventually.become({ + "Sound": { + "canUnderstand": "NO", + "canFulfill": "NO" + }, + "Play": { + "canUnderstand": "YES", + "canFulfill": "YES" + } + }); + }); + }); }); }); From 6afa04eed91a7566c1ad98d630f88aedab1bbd66 Mon Sep 17 00:00:00 2001 From: orly Date: Tue, 23 Oct 2018 14:28:11 +0300 Subject: [PATCH 28/33] add test when no can fulfill intent in request --- test/test_alexa_app_fulfill_intent_request.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/test_alexa_app_fulfill_intent_request.js b/test/test_alexa_app_fulfill_intent_request.js index fe992bd..1f72a73 100644 --- a/test/test_alexa_app_fulfill_intent_request.js +++ b/test/test_alexa_app_fulfill_intent_request.js @@ -17,6 +17,27 @@ describe("Alexa", function () { testApp = new Alexa.app("testApp"); }); + describe("#CanFulfillIntent_regular_intent_request", function () { + var mockRequest = mockHelper.load("intent_request_airport_info.json"); + var request = testApp.request(mockRequest); + + it("valid regular intent request getCanFulfillIntent return undefined", function () { + + var handler = function (req, res) { + handler.result = req.getCanFulfillIntent(); + }; + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(handler); + + var subject = request.then(function (response) { + return handler.result; + }); + return expect(subject).to.eventually.become(undefined); + }); + + }); + describe("#CanFulfillIntent_request", function () { describe("request", function () { From 4131bbfe2eb649ce3f202ed2542b988cdf0dcd37 Mon Sep 17 00:00:00 2001 From: orly Date: Tue, 23 Oct 2018 14:54:52 +0300 Subject: [PATCH 29/33] Update index.js --- index.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 1c5cc4b..b2ab272 100644 --- a/index.js +++ b/index.js @@ -350,10 +350,9 @@ alexa.request = function(json) { }; this.getCanFulfillIntent = function () { - if (!(this.data && this.data.request && this.data.request.intent)) { - return; - } - return this.data.request.intent; + if (this.data && this.data.request && this.data.request.intent) { + return this.data.request.intent; + } }; }; From b47731f6c185f92e08a964a410044307327c4866 Mon Sep 17 00:00:00 2001 From: orly Date: Tue, 23 Oct 2018 15:02:04 +0300 Subject: [PATCH 30/33] add not valid requests tests --- .../can_fulfill_intent_request_no_intent.json | 37 +++++++++++++++++++ test/test_alexa_app_fulfill_intent_request.js | 25 +++++++++++-- 2 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/can_fulfill_intent_request_no_intent.json diff --git a/test/fixtures/can_fulfill_intent_request_no_intent.json b/test/fixtures/can_fulfill_intent_request_no_intent.json new file mode 100644 index 0000000..bee376a --- /dev/null +++ b/test/fixtures/can_fulfill_intent_request_no_intent.json @@ -0,0 +1,37 @@ +{ + "version": "1.0", + "session": { + "new": false, + "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" + } + }, + "context": { + "System": { + "application": { + "applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe" + }, + "user": { + "userId": "amzn1.account.AM3B227HF3FAM1B261HK7FFM3A2", + "accessToken": "ae312e92d87d7b4e7648b19a08f2966a831a59de" + }, + "device": { + "deviceId": "amzn1.ask.device.123456789", + "supportedInterfaces": {} + }, + "apiEndpoint": "https://api.amazonalexa.com", + "apiAccessToken": "123456789" + } + }, + "request": { + "type": "CanFulfillIntentRequest", + "requestId": "amzn1.echo-api.request.e510e005-c518-44c8-930d-746c84e787da", + "locale": "en-US", + "timestamp": "2018-10-18T09:16:28Z" + } +} \ No newline at end of file diff --git a/test/test_alexa_app_fulfill_intent_request.js b/test/test_alexa_app_fulfill_intent_request.js index 1f72a73..47a18e7 100644 --- a/test/test_alexa_app_fulfill_intent_request.js +++ b/test/test_alexa_app_fulfill_intent_request.js @@ -17,11 +17,11 @@ describe("Alexa", function () { testApp = new Alexa.app("testApp"); }); - describe("#CanFulfillIntent_regular_intent_request", function () { - var mockRequest = mockHelper.load("intent_request_airport_info.json"); - var request = testApp.request(mockRequest); + describe("#CanFulfillIntent_not_valid_request", function () { it("valid regular intent request getCanFulfillIntent return undefined", function () { + var mockRequest = mockHelper.load("intent_request_airport_info.json"); + var request = testApp.request(mockRequest); var handler = function (req, res) { handler.result = req.getCanFulfillIntent(); @@ -36,6 +36,23 @@ describe("Alexa", function () { return expect(subject).to.eventually.become(undefined); }); + it("valid request getCanFulfillIntent no intent return undefined", function () { + var mockRequest = mockHelper.load("can_fulfill_intent_request_no_intent.json"); + var request = testApp.request(mockRequest); + + var handler = function (req, res){ + handler.result = req.getCanFulfillIntent(); + }; + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(handler); + + var subject = request.then(function (response) { + return handler.result; + }); + return expect(subject).to.eventually.become(undefined); + }); + }); describe("#CanFulfillIntent_request", function () { @@ -46,7 +63,7 @@ describe("Alexa", function () { var request = testApp.request(mockRequest); beforeEach(function () { request = testApp.request(mockRequest); - }); + }); it("valid request getCanFulfillIntent return intent name PlaySound", function () { From 69b018f4182bfb0c728f6b59dce5b2d9f31b5103 Mon Sep 17 00:00:00 2001 From: orly Date: Wed, 24 Oct 2018 08:42:32 +0300 Subject: [PATCH 31/33] split canFulfillIntent vs canFulfillIntentValue --- index.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index b2ab272..f093615 100644 --- a/index.js +++ b/index.js @@ -188,23 +188,23 @@ alexa.response = function(session) { }; this.canFulfillIntent= function(slots){ - this.canFulfillIntent = new alexa.canFulfillIntent(self.response.response, slots); + this.canFulfillIntentValue = new alexa.canFulfillIntent(self.response.response, slots); return this; }; this.canFulfill = function(canFulfill){ - if(!this.canFulfillIntent){ - this.canFulfillIntent = new alexa.canFulfillIntent(self.response.response); + if(!this.canFulfillIntentValue){ + this.canFulfillIntentValue = new alexa.canFulfillIntent(self.response.response); } - this.canFulfillIntent.canFulfill(canFulfill); + this.canFulfillIntentValue.canFulfill(canFulfill); return this; }; this.canFulfillSlot = function(slotName,canUnderstand, canFulfill){ - if(!this.canFulfillIntent){ - this.canFulfillIntent = new alexa.canFulfillIntent(self.response.response); + if(!this.canFulfillIntentValue){ + this.canFulfillIntentValue = new alexa.canFulfillIntent(self.response.response); } - this.canFulfillIntent.canFulfillSlot(slotName,canUnderstand, canFulfill); + this.canFulfillIntentValue.canFulfillSlot(slotName,canUnderstand, canFulfill); return this; }; }; From 6e5accf7709a912ac4ccc52f04f885142cbff402 Mon Sep 17 00:00:00 2001 From: orly Date: Wed, 24 Oct 2018 08:42:48 +0300 Subject: [PATCH 32/33] add negative tests for no intent --- test/test_alexa_app_fulfill_intent_request.js | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/test/test_alexa_app_fulfill_intent_request.js b/test/test_alexa_app_fulfill_intent_request.js index 47a18e7..c651bfc 100644 --- a/test/test_alexa_app_fulfill_intent_request.js +++ b/test/test_alexa_app_fulfill_intent_request.js @@ -36,7 +36,7 @@ describe("Alexa", function () { return expect(subject).to.eventually.become(undefined); }); - it("valid request getCanFulfillIntent no intent return undefined", function () { + it("valid regular request getCanFulfillIntent no intent return undefined", function () { var mockRequest = mockHelper.load("can_fulfill_intent_request_no_intent.json"); var request = testApp.request(mockRequest); @@ -53,6 +53,59 @@ describe("Alexa", function () { return expect(subject).to.eventually.become(undefined); }); + it("valid regular request no intent response YES for canFulfill", function () { + var mockRequest = mockHelper.load("can_fulfill_intent_request_no_intent.json"); + var request = testApp.request(mockRequest); + + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(function (req, res) { + res.canFulfill("YES"); + }); + + var subject = request.then(function (response) { + return response.response.canFulfillIntent.canFulfill; + }); + return expect(subject).to.eventually.become('YES'); + }); + + it("valid regular request no intent response YES for canFulfill", function () { + var mockRequest = mockHelper.load("can_fulfill_intent_request_no_intent.json"); + var request = testApp.request(mockRequest); + + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(function (req, res) { + res.canFulfill("YES"); + }); + + var subject = request.then(function (response) { + return response.response.canFulfillIntent.canFulfill; + }); + return expect(subject).to.eventually.become('YES'); + }); + + it("valid regular request no intent response with YES for slot Sound", function () { + var mockRequest = mockHelper.load("can_fulfill_intent_request_no_intent.json"); + var request = testApp.request(mockRequest); + + testApp.pre = undefined; + testApp.post = undefined; + testApp.canFulfillIntent(function (req, res) { + res.canFulfillSlot("Sound","YES","YES"); + }); + + var subject = request.then(function (response) { + return response.response.canFulfillIntent.slots; + }); + return expect(subject).to.eventually.become({ + "Sound": { + "canUnderstand": "YES", + "canFulfill": "YES" + } + }); + }); + }); describe("#CanFulfillIntent_request", function () { @@ -97,7 +150,7 @@ describe("Alexa", function () { "name": "Sound", "value": "crickets" }); - }); + }); }); describe("response", function () { From bb256af34382040a380abcb4605508c8d5d36ce1 Mon Sep 17 00:00:00 2001 From: orly Date: Mon, 29 Oct 2018 12:10:21 +0200 Subject: [PATCH 33/33] Update README.md --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8d66a1b..5139901 100644 --- a/README.md +++ b/README.md @@ -563,17 +563,24 @@ See detailed explanation on the Intent and Slot [logic](https://developer.amazon ```javascript -app.displayElementSelected(function(request, response) { +app.canFulfillHandler(function(request, response) { // The request object will return CanFulfillIntent object + //by default the response will include "No" for the intent and slots let canFulfillIntent = request.getCanFulfillIntent(); - //by default the response will include No for the intent and slots //add your logic to determine if you can fulfill the intent and each slot + if(canFulfillIntent.name == 'HowTo' + || canFulfillIntent.name == 'Information' || canFulfillIntent.name == 'Instructions') + { + //we response YES for these intents that we can fulfill + response.canFulfill("YES"); + } + else if(canFulfillIntent.name == 'GetPrice'){ + //In this information we check if we can answer GetPrice intent with the slots values and decide to answer YES - //in this example the response is Yes for the intent and for both slots - response.canFulfill("YES"); - response.canFulfillSlot("Sound","YES","YES"); - response.canFulfillSlot("Another","YES","YES"); + //Do the logic ... + response.canFulfillSlot("MyProduct","YES","YES"); + } }); ```