Learn more about features for advanced voice applications.
Jovo allows you to define multiple staging environments like dev
, test
, and prod
in your app.json
:
{
"stages": {
"dev": {
},
"test": {
},
"prod": {
}
}
}
In a stage, you can define elements that merge with the default options, for example endpoint
, or whole objects like alexaSkill
that include a skillId
(see Staging Example below).
For example, if your endpoint usually is the Jovo Webhook for local development, you can define a different endpoint (e.g. a Lambda ARN) for the dev
environment:
{
"endpoint": "${JOVO_WEBHOOK_URL}",
"stages": {
"dev": {
"endpoint": "<your lambda arn>"
}
}
}
Alternatively, you can also define a defaultStage
if you prefer to have the default options organized in one place:
{
"defaultStage": "local",
"stages": {
"local": {
"endpoint": "${JOVO_WEBHOOK_URL}",
},
"dev": {
"endpoint": "<your lambda arn>"
}
}
}
Different endpoints require changes to the platform specific files. You can build and deploy them for required stage with the following Jovo CLI commands:
# Uses default options or defaultStage, if set
$ jovo build
# Alternative: Uses dev stage
$ jovo build --stage dev
# Then: Deploy to platform
$ jovo deploy
The staging also offers the ability to overwrite config
elements specified in the App Configuration in your app.js
. This way, you can change certain elements, like logging, analytics, or database integrations depending on the stage. Just add a config
object to a stage like this:
{
"stages": {
"dev": {
"endpoint": "<your lambda arn>",
"config": {
"db": {
"type": "dynamodb",
"tableName": "<your table name>"
}
}
}
}
}
To let the framework know which stage the app is currently in, you have two options:
- Define an environment variable
STAGE=<your stage>
and change it according to the current stage. For example, this can be done in an.env
file in the project directory. For AWS Lambda, you can define environment variable in the function configuration:
- Update the
defaultStage
element in theapp.json
.
You can also reference environment variables in the app.json
with ${process.env.<VARIABLE_NAME>}
:
{
"stages": {
"dev": {
"endpoint": "<your lambda arn>",
"config": {
"db": {
"type": "dynamodb",
"tableName": "${process.env.TABLE_NAME}"
}
}
}
}
}
Let's assume we want to build an Alexa Skill that has the following stages:
- A default option for local development that uses the Jovo Webhook and the local FileDB for fast prototyping. The Skill is deployed to the developer's Amazon developer account (
default
ASK profile). - A
dev
stage that is hosted on AWS Lambda with DynamoDB as database. The Skill is deployed to the company's Amazon developer account (company
ASK profile).
{
"alexaSkill": {
"nlu": "alexa",
"skillId": "amzn1.ask.skill.XXX"
},
"endpoint": "${JOVO_WEBHOOK_URL}",
"stages": {
"dev": {
"endpoint": "arn:aws:lambda:us-east-1:XXX",
"alexaSkill": {
"skillId": "amzn1.ask.skill.YYY",
"ask-profile": "company"
},
"config": {
"db": {
"type": "dynamodb",
"tableName": "${process.env.TABLE_NAME}"
}
}
}
}
}
For the local development, it automatically grabs the user's Jovo Webhook url by using the placeholder ${JOVO_WEBHOOK_URL}
. For the alexaSkill
object, there is no need to specify the ask-profile
, as the default
is used.
For the dev
stage, an AWS Lambda ARN is used as endpoint
. Additionally to a different skillId
, the company
profile is used for deployment to the Amazon Developer Portal. Also, this stage specifies a new db
config that differs from the default FileDB and uses a table name referenced in the environment variables in Lambda: ${process.env.TABLE_NAME}
.
You can find even more examples in our knowledge base article.
Plugins allow you to easily extend the Jovo Framework without having to mess with its core code and architecture.
You can find an example file that uses the plugin system on GitHub: examples/appPlugins.js.
Make sure you require
the Jovo Plugin
class. In the example, we do that by modifying the part in our app.js
that imports the jovo-framework
package.
const {App, Plugin} = require('jovo-framework');
A plugin with the name PluginName
can be created like this.
class PluginName extends Plugin {
constructor(options) {
super(options);
}
init() {
// Specify what it does at certain events
}
}
In the app, the plugin can then get registered. The constructor
part of the plugin can be used to define certain options
that the plugin needs to work (e.g. credentials):
app.register(new PluginName());
// If you define options in your constructor
app.register(new PluginName(options));
In init()
, you can define listeners and what to do when a certain event happens. The example below logs the Request Type
for any incoming request:
this.app.on(event, (arguments) => {
// Do something
});
// Example
this.app.on('request', (jovo) => {
console.log(`Request-Type: ${jovo.getPlatform().getRequestType()}`);
});
Here is a list of all events that can be used:
Category | Name | Method | Arguments |
---|---|---|---|
Routing | request | this.app.on('request') |
jovo |
response | this.app.on('response') |
jovo |
|
followUpState | this.app.on('followUpState') |
jovo , state |
|
removeState | this.app.on('removeState') |
jovo |
|
toIntent | this.app.on('toIntent') |
jovo , intent |
|
toStateIntent | this.app.on('toStateIntent') |
jovo , state , intent |
|
toStatelessIntent | this.app.on('toStatelessIntent') |
jovo , intent |
|
endSession | this.app.on('endSession') |
jovo |
|
Output | tell | this.app.on('tell') |
jovo , speech |
ask | this.app.on('ask') |
jovo , speech , repromptSpeech |
|
showSimpleCard | this.app.on('showSimpleCard') |
jovo , title , content |
|
showImageCard | this.app.on('showImageCard') |
jovo , title , content , imageUrl |
|
showAccountLinkingCard | this.app.on('showAccountLinkingCard') |
jovo |
|
Error | handlerError | this.app.on('handlerError') |
jovo , error |
responseError | this.app.on('responseError') |
jovo , error |
The plugin below is called CustomLogging
and enables you to modify what is being logged when. You can find the full example file here: CustomLogging Plugin.
For example, the Request Type
of every request is logged. Also, if a redirect toIntent
is done, this is also logged to be able to follow the user's flow through the app. Finally, the tell
output is logged as well.
class CustomLogging extends Plugin {
constructor(options) {
super(options);
}
init() {
this.app.on('request', (jovo) => {
console.log();
console.log(`Request-Type: ${jovo.getPlatform().getRequestType()}`);
});
this.app.on('toIntent', (jovo, intent) => {
console.log(`toIntent -> ${intent} `);
});
this.app.on('tell', (jovo, speech) => {
console.log(`tell -> ${speech} `);
});
}
}
app.register('CustomLogging', new CustomLogging());
You can now use the Jovo TestSuite to integrate unit tests into your voice app project.
for (let rb of getPlatformRequestBuilder('AlexaSkill', 'GoogleActionDialogFlow')) {
describe('LAUNCH_INTENT', function () {
it('should successfully go into LaunchIntent for ' + rb.type(), function (done) {
send(rb.launch())
.then((res) => {
expect(res.isAsk('Hello World! What\'s your name?', 'Please tell me your name.')).to.equal(true);
done();
});
});
});
}
Unit Testing is a feature that is currently in beta
. For a sample project that uses testing, take a look at this GitHub repository: milksnatcher/DefaultTests.