Skip to content

Commit

Permalink
Merge pull request #250 from VoxaAI/dev
Browse files Browse the repository at this point in the history
Releases v3.3.0
  • Loading branch information
omenocal committed Jul 11, 2019
2 parents 89b246d + 3399198 commit 55d0584
Show file tree
Hide file tree
Showing 18 changed files with 769 additions and 305 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# VOXA Changelog

## 3.3.0 (2019-07-11)

#### New Feature
* Adds S3Persistence plugin

#### Enhancement
* Set not required play audio params in the interface
* Prioritize controllers with an intents array for the same state
* Set unused params as optionals in directives method



## 3.2.1 (2019-06-20)

#### Enhancement
Expand Down
36 changes: 36 additions & 0 deletions docs/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,39 @@ Usage
const app = new VoxaApp({ Model, variables, views });
plugins.autoLoad(app, { adapter });
S3Persistence plugin
--------------------

It stores the user's session attributes in a file in an S3 bucket. You can use this plugin when you host your Node.js code with the Alexa-Hosted skills feature. For more details about how this work, check the `official documentation <https://developer.amazon.com/docs/hosted-skills/build-a-skill-end-to-end-using-an-alexa-hosted-skill.html#persistence>`_.

If you host your code in your own AWS account and plan to use S3 as an storage alternative, keep in mind that you cannot do any Scan or Query operations from S3 and the time to storage and get info is a little longer than DynamoDB.

Params
******

.. js:function:: s3Persistence(app, [config])

S3Persistence plugin uses ``app.onRequestStarted`` to load data every time the user sends a request to the skill
S3Persistence plugin uses ``app.onBeforeReplySent`` to store the user's session data before sending a response back to the skill

:param VoxaApp app: The stateMachineSkill.
:param config: An object with a ``bucketName`` key for the S3 bucket to store the info. A ``pathPrefix`` key in case you want to store this info in a folder. An ``aws`` key if you want to initialize the S3 object with specific values, and an ``s3Client`` key, in case you want to provide an S3 object already initialized.


Usage
******

.. code-block:: javascript
const app = new VoxaApp({ Model, variables, views });
const s3PersistenceConfig = {
bucketName: 'MY_S3_BUCKET',
pathPrefix: 'userSessions',
};
plugins.s3Persistence(app, s3PersistenceConfig);
89 changes: 82 additions & 7 deletions docs/voxa-app.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Voxa Application

:param string stateName: The name of the state
:param function/object handler: The controller to handle the state
:param string/array intent: The intents that this state will handle
:returns: An object or a promise that resolves to an object that specifies a transition to another state and/or a view to render

.. code-block:: javascript
Expand All @@ -46,6 +47,79 @@ Voxa Application
return { tell: 'LaunchIntent.OpenResponse', to: 'die' };
});
Also you can use a shorthand version to define a controller. This is very useful when having a controller that only returns a :ref:`transition <transition>`

.. code-block:: javascript
voxaApp.onState('launch',
{
flow: 'yield'
reply: 'LaunchIntent.OpenResponse',
to: 'nextState'
}
);
You can also set the intent that the controller will handle. If set, any other triggered intent will not enter into the controller.

.. code-block:: javascript
voxaApp.onState("agreed?", {
to: "PurchaseAccepted"
}, "YesIntent");
voxaApp.onState("agreed?", {
to: "TransactionCancelled"
}, ["NoIntent", "CancelIntent"]);
voxaApp.onState("agreed?", {
to: "agreed?",
reply: "Help.ArticleExplanation",
flow: "yield"
}, "HelpIntent");
voxaApp.onState("agreed?", {
to: "agreed?",
reply: "UnknownInput",
flow: "yield"
});
**The order on how you declare your controllers matter in Voxa**

You can set multiple :ref:`controllers <controllers>` for a single state, so how do you know which code will be executed? The first one that Voxa finds. Take this example:

.. code-block:: javascript
voxaApp.onState('ProcessUserRequest', (voxaEvent) => {
// Some code
return { tell: 'ThankYouResponse', to: 'die' };
});
voxaApp.onState('ProcessUserRequest', (voxaEvent) => {
// Some other code
return { tell: 'GoodbyeResponse', to: 'die' };
});
If the state machine goes to the `ProcessUserRequest`, the code running will always be the first one, so the user will always hear the `ThankYouResponse`.

The only scenario where this is overwritten is when you have more than one handler for the same state, and one of them has one or more intents defined. If the user triggers the intent that's inside the list of one-controller intents, Voxa will give it priority. For example, take this code:

.. code-block:: javascript
voxaApp.onState("agreed?", {
to: "PurchaseAccepted"
}, "YesIntent");
voxaApp.onState("agreed?", {
to: "agreed?",
reply: "UnknownInput",
flow: "yield"
});
voxaApp.onState("agreed?", {
to: "TransactionCancelled"
}, ["NoIntent", "CancelIntent"]);
If the user triggers the `NoIntent`, and the state machine goes to the `agreed?` state, the user will listen to the `TransactionCancelled` response, it doesn't matter if the controller is placed above or below a controller without defined intents, the priority will go to the controller with the defined intent.

.. js:method:: VoxaApp.onIntent(intentName, handler)

A shortcut for definining state controllers that map directly to an intent
Expand Down Expand Up @@ -195,15 +269,16 @@ Handle events from the `AudioPlayer interface <https://developer.amazon.com/publ
.. code-block:: javascript
app['onAudioPlayer.PlaybackNearlyFinished']((voxaEvent, reply) => {
const directives = {
type: 'AudioPlayer.Play',
playBehavior: 'REPLACE_ENQUEUED',
token: "",
url: 'https://www.dl-sounds.com/wp-content/uploads/edd/2016/09/Classical-Bed3-preview.mp3',
const playAudio = new PlayAudio({
behavior: "REPLACE_ALL",
offsetInMilliseconds: 0,
};
token: "",
url: 'https://www.dl-sounds.com/wp-content/uploads/edd/2016/09/Classical-Bed3-preview.mp3'
});
return reply.append({ directives });
playAudio.writeToReply(reply);
return reply;
});
Expand Down
30 changes: 15 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "voxa",
"version": "3.2.1",
"version": "3.3.0",
"description": "A fsm (state machine) framework for Alexa, Dialogflow, Facebook Messenger and Botframework apps using Node.js",
"main": "./lib/src/index.js",
"types": "./lib/src/index.d.ts",
Expand Down Expand Up @@ -33,31 +33,31 @@
},
"license": "MIT",
"dependencies": {
"@types/aws-lambda": "^8.10.27",
"@types/aws-lambda": "^8.10.28",
"@types/bluebird": "^3.5.27",
"@types/i18next": "^12.1.0",
"@types/lambda-log": "^2.2.0",
"@types/lodash": "^4.14.134",
"@types/node": "^12.0.8",
"@types/lodash": "^4.14.136",
"@types/node": "^12.6.2",
"@types/request-promise": "^4.1.44",
"@types/tedious": "^4.0.0",
"@types/url-join": "^4.0.0",
"@types/uuid": "^3.4.4",
"actions-on-google": "^2.7.1",
"ask-sdk-model": "^1.16.0",
"@types/uuid": "^3.4.5",
"actions-on-google": "^2.8.0",
"ask-sdk-model": "^1.18.0",
"azure-functions-ts-essentials": "^1.3.2",
"bluebird": "^3.5.5",
"botbuilder": "^3.16",
"fast-xml-parser": "^3.12.16",
"google-auth-library": "^1.6.1",
"googleapis": "^40.0.0",
"i18next": "^17.0.3",
"lambda-log": "^2.2.0",
"lodash": "^4.17.11",
"googleapis": "^40.0.1",
"i18next": "^17.0.6",
"lambda-log": "^2.3.0",
"lodash": "^4.17.14",
"request": "^2.88.0",
"request-promise": "^4.2.4",
"striptags": "^3.1.1",
"url-join": "^4.0.0",
"url-join": "^4.0.1",
"uuid": "^3.3.2"
},
"devDependencies": {
Expand All @@ -76,13 +76,13 @@
"portfinder": "^1.0.20",
"rimraf": "^2.6.3",
"simple-mock": "^0.8.0",
"snyk": "^1.179.0",
"snyk": "^1.192.5",
"source-map-support": "^0.5.12",
"ts-node": "^8.3.0",
"tslint": "^5.17.0",
"tslint": "^5.18.0",
"tslint-no-unused-expression-chai": "^0.1.4",
"typedoc": "^0.14.2",
"typescript": "^3.5.2",
"typescript": "^3.5.3",
"virtual-alexa": "^0.7.2",
"virtual-google-assistant": "^0.3.3"
},
Expand Down
16 changes: 15 additions & 1 deletion src/StateMachine/StateMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,20 @@ export class StateMachine {
throw new UnknownState(currentStateName);
}

return states[0];
if (states.length === 1) {
return states[0];
}

// If the code reaches this point, that means the `states` array may contain
// one state without an intents array filter and/or
// one or more controllers with an intents array that contains the intent name.
// The controller with an intents array is given more priority than the one with no intents array,
// so the first controller that contains the intent name in its intents array is returned.

return (
states.find(
(s: State) => s.intents.length && s.intents.includes(intentName),
) || states[0] // If no state with name is found, the first state is returned by default as an State object is always needed
);
}
}
10 changes: 9 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,18 @@ export {
} from "./errors";
export { Model } from "./Model";

import { autoLoad, replaceIntent, stateFlow } from "./plugins";
import {
autoLoad,
replaceIntent,
s3Persistence,
stateFlow,
} from "./plugins";

export { IS3Persistence } from "./plugins";

export const plugins = {
autoLoad,
replaceIntent,
s3Persistence,
stateFlow,
};

0 comments on commit 55d0584

Please sign in to comment.