# Gadgets: Custom Interfaces
Custom Interfaces (opens new window)
can be defined by Alexa Gadget (opens new window)s, for customizable
interactions with Alexa skills. For handling Custom Interfaces on the skill-side, the @litexa/gadgets
extension provides some shorthand syntax for:
- managing custom event handlers
- handling custom events, and
- sending custom directives.
CUSTOM_INTERFACE
@litexa/gadgets
also adds the required interface to the skill manifest, when needed.
# Installation
The @litexa/gadgets
module can be installed globally, which makes it available to any of your Litexa projects:
npm install -g @litexa/gadgets
If you alternatively prefer installing the extension locally inside your Litexa project for the sake of tracking the dependency, just run the following inside of your Litexa project directory:
npm install --save @litexa/gadgets
This should result in the following directory structure:
project_dir
├── litexa
└── node_modules
└── @litexa
└── gadgets
# Custom Event Handlers
Once a gadget has been configured to support Custom Interfaces (see How To Define and Use A Custom Interface on Your Gadget (opens new window)), an Alexa skill can start listening for events from the gadget by initializing a custom event handler.
This can be done by calling startCustomEventHandler
on an expression which sources the desired handler data:
function myEventHandlerData() {
return {
eventFilter: {
filterExpression: { // only accept events that satisfy this expression
and: [
// namespace should be the name of interface defined by the gadget
{ "==": [{ "var": "header.namespace" }, "Custom.MyGadget"] }
// name should be one of the event names supported by the gadget
{ "==": [{ "var": "header.name" }, "SupportedEventName"] }
]
},
filterMatchAction: "SEND_AND_TERMINATE" // stop handler after first event
},
expiration: {
durationInMilliseconds: 60000, // lifetime of handler, unless stopped
expirationPayload: { // payload to be sent to skill when this handler expires
data: "Some data to be sent to skill upon handler expiration."
}
}
}
}
# someFile.litexa
someState
startCustomEventHandler myEventHandlerData()
startCustomEventHandler will:
- add a handler
token
(unless one was specified) - create a
CustomInterfaceController.StartEventHandler
directive and attach it to the pending skill response - persist the handler token in memory (
@__lastCustomEventHandlerToken
), for validation of received events - start ignoring any previously started event handler for the same namespace, if there is one that hasn't expired
The last started event handler can be halted with stopCustomEventHandler
:
# someFile.litexa
someState
stopCustomEventHandler
stopCustomEventHandler behavior:
- if a custom event handler was started and hasn't expired, a
CustomInterfaceController.StopEventHandler
directive is created (specifying the last started handler's token) and attached to the pending skill response
# Custom Interface Events
Once a skill has an active custom event handler, it can start receiving either of two events:
- A
CustomInterfaceController.EventsReceived
event, when an event sent by the gadget matched the last started event handler'sfilterExpression
. - A
CustomInterfaceController.Expired
event, when an active event handler expired.
# .EventsReceived
An EventsReceived
event request will contain an array of events that were sent by the gadget, where the request
would look something like this:
{
request: {
type: "CustomInterfaceController.EventsReceived",
requestId: /* ... */,
timestamp: /* ... */,
token: /* token of active event handler */,
events: [ // event that passed the filterExpression
{
header: {
namespace: "Custom.MyGadget",
name: "SupportedEventName"
},
endpoint: {
endpointId: "amzn1.ask.endpoint.someId"
},
payload: {
/* arbitrary payload sent by the gadget */
}
}
]
}
}
Such events can be handled in a Litexa skill as follows:
when CustomInterfaceController.EventsReceived "Custom.MyGadget"
# this handler will only trigger if
# 1) there's an active custom event handler for the indicated namespace
# 2) the active custom event handler's token matched the event's
# $event = saved event from request.events that triggered the handler
say "looks like your gadget color is $event.payload.color"
if $event.payload.color == "blue"
say "Let's talk about water."
-> talkAboutWater
else if $event.payload.color == "red"
say "Let's talk about fire."
-> talkAboutFire
when CustomInterfaceController.EventsReceived "Custom.AnotherGadget"
# could handle multiple gadgets, if we started an event handler for each
# .Expired
An Expired
event is received when an active event handler expires:
{
request: {
type: "CustomInterfaceController.Expired",
requestId: /* ... */,
timestamp: /* ... */,
expirationPayload: { /* defined during event handler start */ },
token: /* token of expired handler */
}
}
This can be handled in Litexa as follows:
when CustomInterfaceController.Expired
# @__lastCustomEventHandlerToken = the last started handler's token
if $request.token == @__lastCustomEventHandlerToken
# last started handler expired -> handle it somehow
say "Uh oh. Looks like our handler expired."
else
# an older handler expired, we don't care -> just keep listening for events
LISTEN events
# Custom Interface Directives
Finally, a skill can use CustomInterfaceController.SendDirective
s to send messages to the gadget. To do so,
the skill must first discover the gadgets connected to the user's device in order to detect the "endpoint ID".
For details on how to do so, refer to Querying Available Gadgets (opens new window).
Once the endpoint ID is known, the skill can send a custom directive to that endpoint as follows:
function buildCustomDirective() {
return {
type: 'CustomInterfaceController.SendDirective',
header: {
// some directive name that was registered by the gadget
name: 'SupportedDirectiveName',
// same as for event handlers, name of interface defined by the gadget
namespace: 'Custom.MyGadget'
},
endpoint: {
endpointId: "amzn1.ask.endpoint.someId"
},
payload: { /* any data to be sent to gadget */ }
};
}
# someFile.litexa
someState
directive buildCustomDirective()
# Relevant Resources
For more information, please refer to the official Custom Interfaces (opens new window) documentation: