Contents1Installation12InitialCon2guration33Respondingtoalexaevents54Usingthedevelopmentserver75Respondingtoanintentevent96ProjectSamples1161StarterKit1162MyFirstPodcast1163AccountLinking117Links1371M ID: 869687
Download Pdf The PPT/PDF document "VoxaDocumentationRelease210RainAgencyMar..." is the property of its rightful owner. Permission is granted to download and print the materials on this web site for personal, non-commercial use only, and to display it on your personal computer provided you do not modify the materials and that you retain all copyright notices contained in the materials. By downloading content from our website, you accept the terms of this agreement.
1 VoxaDocumentationRelease2.1.0RainAgencyM
VoxaDocumentationRelease2.1.0RainAgencyMar22,2017 Contents: 1Installation12InitialConguration33Respondingtoalexaevents54Usingthedevelopmentserver75Respondingtoanintentevent96ProjectSamples116.1StarterKit................................................116.2MyFirstPodcast.............................................116.3AccountLinking.............................................117Links137.1Models..................................................137.2ViewsandVariables...........................................147.3Controllers................................................157.4Transition.................................................167.5ThealexaEventObject........................................167.6ThereplyObject............................................177.7Voxa...................................................177.8RequestFlow...............................................227.9I18N.........
2 ........................................
..........................................237.10Plugins..................................................257.11Debugging................................................277.12StarterKit................................................287.13MyFirstPodcast.............................................307.14AccountLinking.............................................34 i ii CHAPTER1 Installation Voxaisdistributedvianpm $npminstallvoxa--save 1 VoxaDocumentation,Release2.1.0 2Chapter1.Installation CHAPTER2 InitialConguration InstantiatingaStateMachineSkillrequiresacongurationspecifyingyourViewsandVariables. usestrict;constVoxa=require(voxa);constviews=require(./views):constvariables=require(./variables);constskill=newVoxa({variables,views}); 3 VoxaDocumentation,Release2.1.0 4Chapter2.InitialConguration CHAPTER3 Respondingtoalexaevents Onceyouhaveyourskillcon
3 guredrespondingtoeventsisassimpleasc
guredrespondingtoeventsisassimpleascallingtheskill.lambdamethod constskill=require(./MainStateMachine);exports.handler=skill.lambda(); 5 VoxaDocumentation,Release2.1.0 6Chapter3.Respondingtoalexaevents CHAPTER4 Usingthedevelopmentserver Theframeworkprovidesasimplebuiltinserverthat'sconguredtoserveallPOSTrequeststoyourskill,thisworksgreatwhendeveloping,speciallywhenpairedwithngrok //thiswillstartanhttpserverlisteningonport3000skill.startServer(3000); 7 VoxaDocumentation,Release2.1.0 8Chapter4.Usingthedevelopmentserver CHAPTER5 Respondingtoanintentevent skill.onIntent(HelpIntent,(alexaEvent)={return{reply:HelpIntent.HelpAboutSkill};});skill.onIntent(ExitIntent,(alexaEvent)={return{reply:ExitIntent.Farewell};}); 9 VoxaDocumentation,Release2.1.0 10Chapter5.Respondingtoanintentevent CHAPTER6 ProjectSamples Tohelpyougetstarte
4 dthestatemachinehasanumberofexampleproje
dthestatemachinehasanumberofexampleprojectsyoucanuse.StarterKitThisisthesimplestproject,itdenesthedefaultdirectorystructurewerecommendusingwithvoxaprojectsandhasanexampleserverless.ymllethatcanbeusedtodeployyourskilltoalambdafunction.MyFirstPodcastInthisexampleyouwillseehowtoimplementapodcastskillbyhavingalistofaudiosinale(podcast.js)withtitlesandurls.ItimplementsallaudiointentsallowedbytheaudiobackgroundfeatureandhandlesalltheplaybackrequestsdispatchedbyAlexaonceanaudiohasstarted,stopped,failed,nishedornearlytonish.Keepinmindtheaudiosmustbehostedinasecureserver.AccountLinkingAmorecomplexprojectthatshowshowtoworkwithaccountlinkingandmakeresponsesusingthemodelstate.Itusesserverlesstodeployyouraccountlinkingserverandskilltolambda,createadynamodbtabletostoreyouraccountlinkingandcreateans3buckettostoreyourstaticassets.ItalsohasagulptasktouploadyourassetstoS3 11 VoxaDocumentation,Relea
5 se2.1.0 12Chapter6.ProjectSamples CHAPTE
se2.1.0 12Chapter6.ProjectSamples CHAPTER7 Links searchModelsModelsarethedatastructurethatholdsthecurrentstateofyourapplication,theframeworkdoesn'tmakemanyassumptionsonitandonlyrequireshaveafromEventmethodthatshouldinitializeitbasedonthealexaEventsessionattributesandaserializemethodthatshouldreturnJSON.stringifyablestructuretothenstoreinthesessionattributes usestrict;const_=require(lodash);classModel{constructor(data){_.assign(this,data);}staticfromEvent(alexaEvent){returnnewthis(alexaEvent.session.attributes.modelData);}serialize(){returnthis;}}module.exports=Model; 13 VoxaDocumentation,Release2.1.0 ViewsandVariablesViewsViewsaretheVoxawayofhandlingrepliestotheuser,they'retemplatesofresponsesthatcanhaveacontextasdenedbyyourvariablesandModelThereare5responsesinthefollowingsnippet:LaunchIntent.OpenResponse,ExitIntent.Farewell,HelpIntent.HelpAboutSkill,Count.SayandCount.te
6 ll constviews={LaunchIntent:{OpenRespons
ll constviews={LaunchIntent:{OpenResponse:{tell:Hello!Good{time}},},ExitIntent:{Farewell:{tell:Ok.Formoreinfovisit{site}site.},},HelpIntent:{HelpAboutSkill:{tell:Formorehelpvisitwww.rain.agency},},Count:{Say:{say:{count}},Tell:{tell:{count}},},}; VariablesVariablesaretherenderingenginewayofaddinglogicintoyourviews.They'redessignedtobeverysimplesincemostofyourlogicshouldbeinyourmodelorcontrollers.Avariablesignatureis:variable(model,alexaEvent)ArgumentsmodelTheinstanceofyourmodelforthecurrentalexaevent.AlexaEventThecurrentalexaevent.ReturnsThevaluetoberenderedorapromiseresolvingtoavaluetoberenderedintheview. constvariables={site:functionsite(model){returnPromise.resolve(example.com);},count:functioncount(model){returnmodel.count;},locale:functionlocale(model,alexaEvent){returnalexaEvent.locale;}}; 14Chapter7.Links VoxaD
7 ocumentation,Release2.1.0 ControllersCon
ocumentation,Release2.1.0 ControllersControllersinyourapplicationcontrolthelogicofyourskill,theyrespondtoalexaalexaEvents,externalresources,manipulatetheinputandgiveproperresponsesusingyourModel,ViewsandVariables.Statescomeinoneoftwoways,theycanbeanobjectofmappingsfromintentnametostate. skill.onState(entry,{LaunchIntent:launch,AMAZON.HelpIntent:help,}); OrtheycanbeafunctionthatgetsaalexaEventobject. skill.onState(launch,(alexaEvent)={return{reply:LaunchIntent.OpenResponse,to:die};}); Yourstateshouldrespondwithatransition.Thetransitionisaplainobjectthatcantakedirectives,toandreplykeys.TheentrycontrollerTheentrycontrollerisspecialinthatit'sthedefaultstatetogotoatthebeginningofyoursessionandifyourstatereturnsnoresponse.Forexampleinthenextsnippedthere'sawaitingstatethatexpectsanAMAZON.NextIntentoranAMAZON.PreviousIntent,int
8 hecasetheuserssayssomethingunexpectedlik
hecasetheuserssayssomethingunexpectedlikeanAMAZON.HelpIntentthestatereturnsundened,theStateMachineframeworkhandlesthissituationsbyredirectingtotheentrystate skill.onState(waiting,(alexaEvent)={if(alexaEvent.intent.name===AMAZON.NextIntent){alexaEvent.model.index+=1;return{reply:Ingredients.Describe,to:waiting}}elseif(alexaEvent.intent.name===AMAZON.PreviousIntent){alexaEvent.model.index-=1;return{reply:Ingredients.Describe,to:waiting}}}); TheonIntenthelperForthesimplepatternofhavingacontrollerrespondtoanspecicintenttheframeworkprovidestheonIntenthelper skill.onIntent(LaunchIntent,(alexaEvent)={return{reply:LaunchIntent.OpenResponse,to:die};}); Underthehoodthiscreatesanewkeyintheentrycontrollerandanewstate 7.3.Controllers15 VoxaDocumentation,Release2.1.0 TransitionAtransi
9 tionistheresultofcontrollerexecution,it'
tionistheresultofcontrollerexecution,it'ssimpleobjectwithsomekeysthatcontroltheowofexecutioninyourskill.toThetokeyshouldbethenameofstateinyourstatemachine,whenpresentitindicatestotheframeworkthatitshouldmovetoanewstate.Ifabsentit'sassumedthattheframeworkshouldmovetothediestate. return{to:stateName}; directivesDirectivesareusedpasseddirectlytothealexaresponse,theformatisdescribedinthealexadocumentation return{directives:{type:AudioPlayer.Play,playBehavior:REPLACE_ALL,url:lesson.Url,offsetInMilliseconds:0,},}; replyThereplykeycantake2forms,asimplestringpointingtooneofyourviewsoraReplyobject. return{reply:LaunchIntent.OpenResponse};constreply=newReply(alexaEvent,{tell:Hithere!});return{reply}; ThealexaEventObjectclassAlexaEvent(event,lambdaContext)ThealexaEventobjectcontainsalltheinformationfromtheAlexaevent,it'sanobjectkeptfortheentirelifecycleofthe
10 statemachinetransitionsandassuchisaperfe
statemachinetransitionsandassuchisaperfectplaceformiddlewaretoputinformationthatshouldbeavailableoneveryrequest.AlexaEvent.modelThedefaultmiddlewareinstantiatesaModelandmakesitavailablethroughalexaEvent.modelAlexaEvent.intent.paramsThealexaEventobjectmakesintent.slotsavailablethroughintent.paramsafteraplyingasim-pletransformationso{slots:[{name:Dish,value:FriedChicken}]}becomes{Dish:FriedChicken}AlexaEvent.userAconveniencegettertoobtaintheuserfromsesssion.userorcontext.System.user 16Chapter7.Links VoxaDocumentation,Release2.1.0 ThereplyObjectclassReply(alexaEvent[,message])ThereplyobjectisusedbytheframeworktorenderAlexaresponses,ittakesallofyourstatements,cardsanddirectivesandgeneratesaproperjsonresponseforAlexaArgumentsalexaEvent(AlexaEvent)messageAmessageobjectReply.append(message)AddsstatementstotheReplyArgumentsmessageAnobjectwithk
11 eysask,tell,say,reprompt,cardordirective
eysask,tell,say,reprompt,cardordirectiveskeys.OranotherreplyobjectReturnstheReplyobjectReply.toJSON()ReturnsAnobjectwiththeproperformattosendbacktoAlexa,withstatementswrappedinSSMLtags,cards,repromptsanddirectivesVoxaclassVoxa(cong)ArgumentsconfigCongurationforyourskill,itshouldincludeViewsandVariablesandoptionallyamodelandalistofappIds.IfappIdsispresentthentheframeworkwillcheckeveryalexaeventandenforcetheapplicationidtomatchoneofthespeciedapplicationids. constskill=newVoxa({Model,variables,views,appIds}); Voxa.lambda()ReturnsAlambdahandlerthatwillcallyourskill.executemethod exports.handler=skill.lambda(); Voxa.execute(event)ThemainentrypointfortheSkillexecutionArgumentseventTheeventsentbyalexa.contextThecontextofthelambdafunctionReturnsPromiseAresponseresolvingtoajavascriptobjecttobesentasaresulttoAlexa. skill.execute(event,context).then(resultʏ
12 E00;=callback(null,result)).catch(callba
E00;=callback(null,result)).catch(callback); 7.6.ThereplyObject17 VoxaDocumentation,Release2.1.0 Voxa.onState(stateName,handler)MapsahandlertoastateArgumentsstateName(string)Thenameofthestatehandler(function/object)ThecontrollertohandlethestateReturnsAnobjectorapromisethatresolvestoanobjectthatspeciesatransitiontoanotherstateand/oraviewtorender skill.onState(entry,{LaunchIntent:launch,AMAZON.HelpIntent:help,});skill.onState(launch,(alexaEvent)={return{reply:LaunchIntent.OpenResponse,to:die};}); Voxa.onIntent(intentName,handler)AshortcutfordeniningstatecontrollersthatmapdirectlytoanintentArgumentsintentName(string)Thenameoftheintenthandler(function/object)ThecontrollertohandlethestateReturnsAnobjectorapromisethatresolvestoanobjectthatspeciesatransitiontoanothers
13 tateand/oraviewtorender skill.onIntent(&
tateand/oraviewtorender skill.onIntent(HelpIntent,(alexaEvent)={return{reply:HelpIntent.HelpAboutSkill};}); Voxa.onIntentRequest(callback[,atLast])ThisisexecutedforallIntentRequestevents,defaultbehavioristoexecutetheStateMachinemachinery,yougenerallydon'tneedtooverridethis.Argumentscallback(function)last(bool)ReturnsPromiseVoxa.onLaunchRequest(callback[,atLast])AddsacallbacktobeexecutedwhenprocessingaLaunchRequest,thedefaultbehavioristofakethealexaeventasanIntentRequestwithaLaunchIntentandjustdefertotheonIntentRequesthandlers.Yougenerallydon'tneedtooverridethis.Voxa.onBeforeStateChanged(callback[,atLast])Thisisexecutedbeforeenteringeverystate,itcanbeusedtotrackstatechangesormakechangestothealexaeventobjectVoxa.onBeforeReplySent(callback[,atLast])Addsacallbacktobeexecutedjustbeforesendingthereply,internallythisisusedtoaddtheserializedmodelandnext
14 statetothesession.Itcanbeusedtoalterther
statetothesession.Itcanbeusedtoalterthereply,orforexampletotrackthenalresponsesenttoauserinanalytics. 18Chapter7.Links VoxaDocumentation,Release2.1.0 skill.onBeforeReplySent((alexaEvent,reply)={constrendered=reply.write();analytics.track(alexaEvent,rendered)}); Voxa.onAfterStateChanged(callback[,atLast])Addscallbackstobeexecutedontheresultofastatetransition,thisarecalledaftereverytransitionandinternallyit'susedtorenderthetransitionreplyusingtheviewsandvariablesThecallbacksgetalexaEvent,replyandtransitionparams,itshouldreturnthetransitionobject skill.onAfterStateChanged((alexaEvent,reply,transition)={if(transition.reply===LaunchIntent.PlayTodayLesson){transition.reply=_.sample([LaunchIntent.PlayTodayLesson1,LaunchIntent.,!PlayTodayLesson2]);}returntransition;}); Voxa.onUnhandledState(callback[,atLast])Addsacallbacktobeexecutedwhenastatetransiti
15 onfailstogeneratearesult,thisusuallyhapp
onfailstogeneratearesult,thisusuallyhappenswhenredirectingtoamissingstateoranentrycallforanonconguredintent,thehandlersgetaalexaeventparameterandshouldreturnatransitionthesameasastatecontrollerwould.Voxa.onSessionStarted(callback[,atLast])AddsacallbacktotheonSessinStartedevent,thisexecutesforalleventswherealexaEvent.session.new===trueThiscanbeusefultotrackanalytics skill.onSessionStarted((alexaEvent,reply)={analytics.trackSessionStarted(alexaEvent);}); Voxa.onRequestStarted(callback[,atLast])Addsacallbacktobeexecutedwheneverthere'saLaunchRequest,IntentRequestoraSessionEndedRequest,thiscanbeusedtoinitializeyouranalyticsorgetyouraccountlinkinguserdata.Internallyit'susedtoinitializethemodelbasedontheeventsession skill.onRequestStarted((alexaEvent,reply)={alexaEvent.model=this.config.Model.fromEvent(alexaEvent);}); Voxa.onSessionEnded(callback[,atLast])AddsacallbacktotheonSess
16 ionEndedevent,thisiscalledforeverySessio
ionEndedevent,thisiscalledforeverySessionEndedRequestorwhentheskillreturnsatransitiontoastatewhereisTerminal===true,normallythisisatransitiontothediestate.YouwouldnormallyusethistotrackanalyticsVoxa.onSystem.ExceptionEncountered(callback[,atLast])ThishandlesSystem.ExceptionEncounteredeventthataresenttoyourskillwhenaresponsetoanAudioPlayereventcausesanerror returnPromise.reduce(errorHandlers,(result,errorHandler)={if(result){returnresult;} 7.7.Voxa19 VoxaDocumentation,Release2.1.0 returnPromise.resolve(errorHandler(alexaEvent,error));},null); ErrorhandlersYoucanregistermanyerrorhandlerstobeusedforthedifferentkindoferrorstheapplicationcouldgenerate.Theyallfollowthesamelogicwhereifthersterrortypeisnothandledthenthedefaultistobedeferredtothemoregeneralerrorhandlerthatultimatelyjustreturnsadefaulterrorreply.They'reexecutedsequentiallyandwillstopwhenthersthandlerreturnsareply.Voxa.onSta
17 teMachineError(callback[,atLast])Thishan
teMachineError(callback[,atLast])ThishandlerwillcatchallerrorsgeneratedwhentryingtomaketransitionsinthestateMachine,thiscouldincludeerrorsinthestatemachinecontrollers,,thehandlersget(alexaEvent,reply,error)param-eters skill.onStateMachineError((alexaEvent,reply,error)={//itgetsthecurrentreply,whichcouldbeincompleteduetoanerror.returnnewReply(alexaEvent,{tell:Anerrorinthecontrollerscode}).write();}); Voxa.onError(callback[,atLast])Thisisthemoregeneralhandlerandwillcatchallunhandlederrorsintheframework,itgets(alexaEvent,error)parametersasarguments skill.onError((alexaEvent,error)={returnnewReply(alexaEvent,{tell:Anunrecoverableerroroccurred.}).write();}); PlaybackControllerhandlersHandleeventsfromtheAudioPlayerinterfaceaudioPlayerCallback(alexaEvent,reply)AllaudioplayermiddlewarecallbacksgetaalexaeventandareplyobjectArgumentsalexaEvent(AlexaEvent)
18 ThealexaeventsentbyAlexareply(obje
ThealexaeventsentbyAlexareply(object)AreplytobesentasaresponseReturnsobjectwriteYouralexaeventhandlershouldreturnanappropriateresponseaccordingtotheeventtype,thisgenerallymeansappendingtothereplyobjectInthefollowingexamplethealexaeventhandlerreturnsaREPLACE_ENQUEUEDdirectivetoaPlaybackNearlyFinished()event. skill[onAudioPlayer.PlaybackNearlyFinished]((alexaEvent,reply)={constdirectives={type:AudioPlayer.Play,playBehavior:REPLACE_ENQUEUED,token:"",url:https://www.dl-sounds.com/wp-content/uploads/edd/2016/09/Classical-Bed3-,!preview.mp3, 20Chapter7.Links VoxaDocumentation,Release2.1.0 offsetInMilliseconds:0,};returnreply.append({directives});}); Voxa.onAudioPlayer.PlaybackStarted(callback[,atLast])Voxa.onAudioPlayer.PlaybackFinished(callback[,atLast])Voxa.onAudioPlayer.PlaybackStopped(callback[,atLast])Voxa.onAudioPlayer.PlaybackFailed(cal
19 lback[,atLast])Voxa.onAudioPlayer.Playba
lback[,atLast])Voxa.onAudioPlayer.PlaybackNearlyFinished(callback[,atLast])Voxa.onPlaybackController.NextCommandIssued(callback[,atLast])Voxa.onPlaybackController.PauseCommandIssued(callback[,atLast])Voxa.onPlaybackController.PlayCommandIssued(callback[,atLast])Voxa.onPlaybackController.PreviousCommandIssued(callback[,atLast]) 7.7.Voxa21 VoxaDocumentation,Release2.1.0 RequestFlow 22Chapter7.Links VoxaDocumentation,Release2.1.0 I18NInternationalizationsupportisdoneusingthei18nextlibrary,thesametheAmazonAlexaNodeSDKuses.ConguringtheskillforI18NTouseityouneedtocongureyourskilltousetheI18NRendererclassinsteadofthedefaultrendererclass. constVoxa=require(voxa);constskill=newVoxa({Model,variables,views,RenderClass:Voxa.I18NRenderer}); TheframeworktakescareofselectingthecorrectlocaleoneveryalexaeventbylookingatthealexaEvent.request.localeproperty.ChangesinyourviewsTheotherchangeyouwillneedi
20 stodeneyourviewsusingthei18nexttrans
stodeneyourviewsusingthei18nexttranslateformat: usestrict;constviews=(functionviews(){return{en-us:{translation:{LaunchIntent:{OpenResponse:{tell:Hello!Good{time}},},Question:{Ask:{ask:Whattimeisit?},},ExitIntent:{Farewell:{tell:Ok.Formoreinfovisit{site}site.},},Number:{One:{tell:{numberOne}},},},},de-de:{translation:{LaunchIntent:{OpenResponse:{tell:Hallo!guten{time}},},Question:{Ask:{ask:wiespätistes?},},ExitIntent:{Farewell:{tell:OkfürweitereInfosbesuchen{site}Website},},Number:{One:{tell:{numberOne}},},},},}; 7.9.I18N23 VoxaDocumentation,Release2.1.0 }());module.exports=views; VariablesVariablesshouldworkmostlythesameaswiththeDefaultRenderer,withtheexceptionthatvariableswillnowgetalocalekey usestrict;/***Variablesfortests**Copyright(c)2016RainAgency.*LicensedundertheMITlic
21 ense.*/constPromise=require(bluebir
ense.*/constPromise=require(bluebird);constvariables={time:functiontime(){consttoday=newDate();constcurHr=today.getHours();if(curHr12){returnPromise.resolve(Morning);}if(curHr18){returnPromise.resolve(Afternoon);}returnPromise.resolve(Evening);},site:functionsite(){returnPromise.resolve(example.com);},count:functioncount(model){returnmodel.count;},numberOne:functionnumberOne(model,request){if(request.request.locale===en-us){returnone;}elseif(request.request.locale===de-de){returnein;}return1;},};module.exports=variables; 24Chapter7.Links VoxaDocumentation,Release2.1.0 PluginsPluginsallowyoutomodifyhowtheStateMachineSkillhandlesanalexaevent.Whenapluginisregistereditwillusethedifferenthookpointsinyourskilltoaddfunctionality.Ifyouhaveseveralskillswithsimilarbehaviorthenyouransweristocreateaplugin.UsingapluginAfteri
22 nstatiatingaStateMachineSkillyoucanregis
nstatiatingaStateMachineSkillyoucanregisterpluginsonit.BuiltinpluginscanbeaccessedthroughVoxa.plugins usestrict;constVoxa=require(voxa);constModel=require(./model);constviews=require(./views):constvariables=require(./variables);constskill=newVoxa({Model,variables,views});Voxa.plugins.replaceIntent(skill); StateFlowpluginStoresthestatetransitionsforeveryalexaeventinanarray.stateFlow(skill)StateFlowattachescallbackstoonRequestStarted(),onBeforeStateChanged()andonBeforeReplySent()totrackstatetransitionsinaalexaEvent.flowarrayArgumentsskill(Voxa)TheskillobjectUsage constalexa=require(alexa-statemachine);alexa.plugins.stateFlow.register(skill)skill.onBeforeReplySent((alexaEvent)={console.log(alexaEvent.flow.join());//entryfirstStatesecondStatedie}); Replaceintentplu
23 ginItallowsyoutorenameanintentnamebasedo
ginItallowsyoutorenameanintentnamebasedonaregularexpression.Bydefaultitwillmatch/(.*)OnlyIntent$/andreplaceitwith$1Intent.replaceIntent(skill[,cong])ReplaceIntentpluginusesonIntentRequest()tomodifytheincommingrequestintentnameArgumentsskill(Voxa)ThestateMachineSkill 7.10.Plugins25 VoxaDocumentation,Release2.1.0 configAnobjectwiththeregextolookforandthereplacevalue.Usage constskill=newVoxa({Model,variables,views});Voxa.plugins.replaceIntent(skill,{regex:/(.*)OnlyIntent$/,replace:$1Intent});Voxa.plugins.replaceIntent(skill,{regex:/^VeryLong(.*)/,replace:Long$1}); WhyonlyIntents?Agoodpracticeistoisolateanutteranceintoanotherintentifitcontainsasingleslot.Bycreatingtheonlyintent,alexawillprioritizethisintentiftheusersaysonlytheslot.Let'sexplainwiththefollowingscenario.Youneedtheusertoprovideazipcode.soyoushouldhaveanintentcalledZipCodeIntent.Butyoustillhavet
24 omanageiftheuseronlysaysitszipcodewithno
omanageiftheuseronlysaysitszipcodewithnootherwordsonit.Sothat'swhenwecreateanOnlyIntent.Let'scalledZipCodeOnlyIntent.Ourutterancelewillbelikethis: ZipCodeIntenthereismy{ZipCodeSlot}ZipCodeIntentmyzipis{ZipCodeSlot}...ZipCodeOnlyIntent{ZipCodeSlot} Butnowwehavetwostateswhicharebasicallythesame.ReplaceintentpluginwillrenameallincomingrequestsintentsfromZipCodeOnlyIntenttoZipCodeIntent.CloudwatchpluginItlogsaCloudWatchmetricwhentheskillcatchesanerrororsuccessexecution.Paramscloudwatch(skill,cloudwatch[,eventMetric])CloudwatchpluginusesVoxa.onError(),Voxa.onStateMachineError()andVoxa.onBeforeReplySent()tologmetricsArgumentsskill(Voxa)ThestateMachineSkillcloudwatchAnewAWS.CloudWatchobject.putMetricDataParamsParamsforputMetricDataUsage constAWS=require(aws-sdk);constskill=newVoxa({Model,variables,views});constcloudWatch=newAWS.CloudWatch({}); 26Chapter7.Lin
25 ks VoxaDocumentation,Release2.1.0 conste
ks VoxaDocumentation,Release2.1.0 consteventMetric={MetricName:CaughtError,//NameofyourmetricNamespace:SkillName//Nameofyourskill};Voxa.plugins.cloudwatch(skill,cloudWatch,eventMetric); AutoloadpluginItacceptsanadaptertoautoloadinfointothemodelobjectcomingineveryalexarequest.ParamsautoLoad(skill[,cong])Autoloadpluginusesskill.onSessionStartedtoloaddatathersttimeuseropenaskillArgumentsskill(Voxa)ThestateMachineSkill.configAnobjectwithanadapterkeywithagetPromisemethodinwhichyoucanhandleyourdatabaseaccesstofetchinformationfromanyresource.Usage constskill=newVoxa({Model,variables,views});Voxa.plugins.autoLoad(skill,{adapter}); DebuggingVoxausesthedebugmoduleinternallytologanumberofdifferentinternalevents,ifyouwanthavealookatthoseeventsyouhavetodeclarethefollowingenvironmentvariableDEBUG=voxaThisisanexampleofthelogoutput voxaReceivednewevent:{"version":"
26 1.0","session":{"new":true,"sessionId":,
1.0","session":{"new":true,"sessionId":,!"SessionId.09162f2a-cf8f-414f-92e6-1e3616ecaa05","application":{"applicationId":,!"amzn1.ask.skill.1fe77997-14db-409b-926c-0d8c161e5376"},"attributes":{},"user":{,!"userId":"amzn1.ask.account.","accessToken":""}},"request":{"type":"LaunchRequest",,!"requestId":"EdwRequestId.0f7b488d-c198-4374-9fb5-6c2034a5c883","timestamp":"2017-,!01-25T23:01:15Z","locale":"en-US"}}+0msvoxaInitializedmodellike{}+8msvoxaStartingthestatemachinefromentrystate+2svoxaRunningsimpleTransitionforentry+1msvoxaRunningonAfterStateChangeCallbacks+0msvoxaentrytransitionresultedin{"to":"launch"}+0msvoxaRunninglaunchenterfunction+1msvoxaRunningonAfterStateChangeCallbacks+0msvoxalaunchtransitionresultedin{"reply":"Intent.Launch","to":"entry","message":{,!"tell":"Welcomemail@example.com!"},"session":{"data":{},"reply":null}}+7ms 7.11.Debugging27 VoxaDocumentation,Release2.1.0 StarterKitThispro
27 jectisdesignedtobeasimpletemplateforyour
jectisdesignedtobeasimpletemplateforyournewskills.Withsomewellthoughtdefaultsthathaveprovenusefulwhendevelopingreallifeskills.DirectoryStructureIthasthefollowingdirectorystructure .-README.md-config|-env.js|-index.js|-local.json.example|-production.json|-staging.json-gulpfile.js-package.json-serverless.yml-services-skill|-MainStateMachine.js|-index.js|-variables.js|-views.js-speechAssets|-IntentSchema.json|-SampleUtterances.txt|-customSlotTypes-test-server.js congBydefaultyourskillwillhavethefollowingenvironments:localstagingproductionWhatenvironmentyou'reisdeterminedintheconfig/env.jsmoduleusingthefollowingcode: usestrict;functiongetEnv(){if(process.env.NODE_ENV)returnprocess.env.NODE_ENV;if(process.env.AWS_LAMBDA_FUNCTION_NAME){//TODOputyourownlambdafunctionnamehereif(process.env.AWS_LAMBDA_FUNCTION_NAME===)returnproduction;returnstaging
28 ; 28Chapter7.Links VoxaDocumentation,Rel
; 28Chapter7.Links VoxaDocumentation,Release2.1.0 }returnlocal;}module.exports=getEnv(); skillThisiswhereyourcodetohandlealexaeventsgoes,youwillusuallyhaveaStateMachinedenition,thiswillincludestates,middlewareandaModel,ViewsandVariablesspeechAssetsThisshouldbeaversioncontrolledcopyofyourintentschema,sampleutterrancesandcustomslots.server.jsAnhttpserverforyourskillconguredtolistenonport3000,thisshouldbeusedfordevelopmentonly.servicesJustacommonplacetoputmodelsandlibrariestestYouwritetestsright?gulpleAgulprunnerconguredwithawatchtaskthatstartsyourexpressserverandlistensforchangestoreloadyourapplication.serverless.ymlTheserverlessframeworkisatoolthathelpsyoumanageyourlambdaapplications,assumingyouhaveyourAWScredentialssetupproperlythisstarterkitdenestheveryminimumneededsoyoucandeployyourskilltolambdawiththefollowingcommand: $slsdeploy Runningtheproject1.Clonethevoxareposito
29 ry2.Createanewskillprojectusingthesample
ry2.Createanewskillprojectusingthesamples/starterKitdirectoryasabasis3.Makesureyou'rerunningnode4.3,thisiseasiestwithnvm 7.12.StarterKit29 VoxaDocumentation,Release2.1.0 4.Createaconfig/local.jsonleusingconfig/local.json.exampleasanexample5.Runtheprojectwithgulpwatch6.Atthispointyoushouldstartngrokhttp3000andcongureyourskillintheAmazonDeveloperpaneltousethengrokhttpsendpoint.MyFirstPodcastThisprojectwillhelpyoubuildapodcastskillusingtheAudiodirectivestemplate.Youwillbeabletomanageloop,shuferequestsaswellasoffertheuserthepossibilitytostartanaudioover,pause,stopitorplaythenextorpreviousaudiofromapodcastlist.DirectoryStructureIthasthefollowingdirectorystructure .-README.md-config|-env.js|-index.js|-local.json.example|-production.json|-staging.json-gulpfile.js-package.json-serverless.yml-services-skill|-data||-podcast.js|-MainStateMachine.js|-index.js|-states.js|-variables.js|-views.js-speech
30 Assets|-IntentSchema.json|-SampleUtteran
Assets|-IntentSchema.json|-SampleUtterances.txt|-customSlotTypes-test-server.js congBydefaultyourskillwillhavethefollowingenvironments:localstagingproductionWhatenvironmentyou'reisdeterminedintheconfig/env.jsmoduleusingthefollowingcode: 30Chapter7.Links VoxaDocumentation,Release2.1.0 usestrict;functiongetEnv(){if(process.env.NODE_ENV)returnprocess.env.NODE_ENV;if(process.env.AWS_LAMBDA_FUNCTION_NAME){//TODOputyourownlambdafunctionnamehereif(process.env.AWS_LAMBDA_FUNCTION_NAME===)returnproduction;returnstaging;}returnlocal;}module.exports=getEnv(); skillindex.jsFirstleinvokedbythelambdafunction,itinitializesthestatemachine.Youdon'tneedtomodifythisle.MainStateMachine.jsStatemachineisinitializedwithyourmodel,viewsandvariables.Theclassstates.jswillbeinchargetohandleallintentsandeventscomingfromAlexa.Youdon'tneedtomodifythis&
31 #2;le.states.jsAlleventsandintentsdispat
#2;le.states.jsAlleventsandintentsdispatchedbytheAlexaVoiceServicetoyourskillarehandledhere.YoucanintegrateanyothermoduleorAPIcallstothirdpartyservices,calldatabaseresourcesorjustsimplyreplyaHelloorGoodbyeresponsetotheuser.Theaudiointentshandledinthisexampleare:AMAZON.CancelIntentAMAZON.LoopOffIntentAMAZON.LoopOnIntentAMAZON.NextIntentAMAZON.PauseIntentAMAZON.PreviousIntentAMAZON.RepeatIntentAMAZON.ResumeIntentAMAZON.ShufeOffIntentAMAZON.ShufeOnIntentAMAZON.StartOverIntentYoucantrackthevaluesforloop,shufeandcurrentURLplayinginthetokenpropertyoftheAlexaeventinthepathalexaEvent.context.AudioPlayer.token: 7.13.MyFirstPodcast31 VoxaDocumentation,Release2.1.0 skill.onState(loopOff,(alexaEvent)={if(alexaEvent.context){consttoken=JSON.parse(alexaEvent.context.AudioPlayer.token);constshuffle=token.shuffle;cons
32 tloop=0;constoffsetInMilliseconds=alexaE
tloop=0;constoffsetInMilliseconds=alexaEvent.context.AudioPlayer.,!offsetInMilliseconds;letindex=token.index;if(index===podcast.length){index=0;}constdirectives=buildPlayDirective(podcast[index].url,index,shuffle, ,!loop,offsetInMilliseconds);return{reply:Intent.LoopDeactivated,to:die,directives};}return{reply:Intent.Exit,to:die};}); ForanyoftheseeventsyoucanmakeAlexatospeakafteruser'sactionwithareplyobject,optionallyyoucandenethediestateandpassthroughthedirectivesobjectwitheitheraAudioPlayer.PlayorAudioPlayer.Stopdirectivetype.Youcanalsohandledthefollowingplaybackrequestevents:AudioPlayer.PlaybackStartedAudioPlayer.PlaybackFinishedAudioPlayer.PlaybackStoppedAudioPlayer.PlaybackNearlyFinishedAudioPlayer.PlaybackFailedYou'renotallowedtorespondwithareplyobjectsinceit'sjustaneventmostfortrackignpurposes,soit'soptionaltoimplementan
33 dyoucandothefollowingsyntax: skill[
dyoucandothefollowingsyntax: skill[onAudioPlayer.PlaybackStarted]((alexaEvent)={console.log(onAudioPlayer.PlaybackStarted,JSON.stringify(alexaEvent,null, ,!2));}); IncasetheuserhasactivatedtheloopmodebydispatchingtheAMAZON.LoopOnIntentintent,youcanimplementaqueuelistintheAudioPlayer.PlaybackNearlyFinishedthisway: skill[onAudioPlayer.PlaybackNearlyFinished]((alexaEvent,reply)={consttoken=JSON.parse(alexaEvent.context.AudioPlayer.token);if(token.loop===0){returnreply;}constshuffle=token.shuffle;constloop=token.loop;letindex=token.index+1;if(shuffle===1){ 32Chapter7.Links VoxaDocumentation,Release2.1.0 index=randomIntInc(0,podcast.length-1);}elseif(index===podcast.length){index=0;}constdirectives=buildEnqueueDirective(podcast[index].url,index,shuffle, ,!loop);returnreply.append({directives});});functionbuildEnqueueDirective(url,index,shuffle,loop){co
34 nstdirectives={};directives.type=Au
nstdirectives={};directives.type=AudioPlayer.Play;directives.playBehavior=REPLACE_ENQUEUED;directives.token=createToken(index,shuffle,loop);directives.url=podcast[index].url;directives.offsetInMilliseconds=0;returndirectives;} ThebuildEnqueueDirectivefunctionisinchargetobuildadirectiveobjectwithaqueuebehavior,whichwillallowtheskilltoplaythenextaudioassoonasthecurrentoneisnished.Thisiswhereyourcodetohandlealexaeventsgoes,youwillusuallyhaveaStateMachinedenition,thiswillincludestates,middlewareandaModel,ViewsandVariables.data/podcast.jsAJSONvariablewithtitlesandurlsfor5audioexampleshostedinasecureserver,allalongplayapodcastwhichtheusercanshufeorloop.Youcanmodifythislewithwhateverotheraudiotoaddtoyourplaylist.Keepinmindthattheymustbehostedinasecureserver.ThesupportedformatsfortheaudioleincludeAAC/MP4,MP3,HLS,PLSandM3U.Bitrates:16kbpsto384kbps.speechAssetsThisshould
35 beaversioncontrolledcopyofyourintentsche
beaversioncontrolledcopyofyourintentschema,sampleutterrancesandcustomslots.server.jsAnhttpserverforyourskillconguredtolistenonport3000,thisshouldbeusedfordevelopmentonly.servicesJustacommonplacetoputmodelsandlibrariestestYouwritetestsright? 7.13.MyFirstPodcast33 VoxaDocumentation,Release2.1.0 gulpleAgulprunnerconguredwithawatchtaskthatstartsyourexpressserverandlistensforchangestoreloadyourapplication.serverless.ymlTheserverlessframeworkisatoolthathelpsyoumanageyourlambdaapplications,assumingyouhaveyourAWScredentialssetupproperlythisstarterkitdenestheveryminimumneededsoyoucandeployyourskilltolambdawiththefollowingcommand: $slsdeploy Runningtheproject1.Clonethevoxarepository2.Createanewskillprojectusingthesamples/my-first-podcastdirectoryasabasis3.Makesureyou'rerunningnode4.3,thisiseasiestwithnvm4.Createaconfig/local.jsonleusingconfig/local.json.exampleasanexample5.Runtheprojectwith
36 gulpwatch6.CreateaskillinyourAmazonDevel
gulpwatch6.CreateaskillinyourAmazonDeveloperPortalaccountundertheALEXAmenu.7.GototheinteractionmodeltabandcopytheintentschemaandutterancesfromthethespeechAssetsfolder.8.Atthispointyoushouldstartngrokhttp3000andcongureyourskillintheAmazonDeveloperpaneltousethengrokhttpsendpoint.AccountLinkingThisprojectisdesignedtobeasimpletemplateforyournewskillswithaccountlinking.User'sinformationisstoredinaDynamoDBtablesoyoucanfetchitfromtheskillonceusersareauthenticated.DirectoryStructureIthasthefollowingdirectorystructure .-README.md-config|-env.js|-index.js|-local.json.example|-production.json|-staging.json-gulpfile.js-package.json-serverless.yml-services|-model.js 34Chapter7.Links VoxaDocumentation,Release2.1.0 |-userStorage.js-skill|-MainStateMachine.js|-index.js|-states.js|-variables.js|-views.js-speechAssets|-IntentSchema.json|-SampleUtterances.txt|-customSlotTypes-test-www-infrastructure|-mount.js-route
37 s|-index.js|-skill.js-server.js cong
s|-index.js|-skill.js-server.js congBydefaultyourskillwillhavethefollowingenvironments:localstagingproductionWhatenvironmentyou'reisdeterminedintheconfig/env.jsmoduleusingthefollowingcode: usestrict;functiongetEnv(){if(process.env.NODE_ENV)returnprocess.env.NODE_ENV;if(process.env.AWS_LAMBDA_FUNCTION_NAME){//TODOputyourownlambdafunctionnamehereif(process.env.AWS_LAMBDA_FUNCTION_NAME===)returnproduction;returnstaging;}returnlocal;}module.exports=getEnv(); skillindex.jsFirstleinvokedbythelambdafunction,itinitializesthestatemachine.Youdon'tneedtomodifythisle. 7.14.AccountLinking35 VoxaDocumentation,Release2.1.0 MainStateMachine.jsStatemachineisinitializedwithyourmodel,viewsandvariables.Theclassstates.jswillbeinchargetohandleallintentsandeventscomingfromAlexa.Youdon'tneedtomodifythisle.states.jsAlleventsandintentsdispatch
38 edbytheAlexaVoiceServicetoyourskillareha
edbytheAlexaVoiceServicetoyourskillarehandledhere.YoucanintegrateanyothermoduleorAPIcallstothirdpartyservices,calldatabaseresourcesorjustsimplyreplyaHelloorGoodbyeresponsetotheuser.Beforetheverybeginningofthelesson,youcanimplementthemethodonRequestStartedtofetchuser'sdatafromDynamoDBbasedontheaccessTokencomingfromAlexa skill.onRequestStarted((alexaEvent)={if(!alexaEvent.session.user.accessToken){returnalexaEvent;}conststorage=newUserStorage();returnstorage.get(alexaEvent.session.user.accessToken).then((user)={alexaEvent.model.user=user;returnalexaEvent;});}); IftheuserisnotauthenticatedyoucanalsosendaLinkingAccountcardtotheAlexaappsousersknowthatbeforeusingyourskill,theymustgetauthenticated.speechAssetsThisshouldbeaversioncontrolledcopyofyourintentschema,sampleutterrancesandcustomslots.wwwAstandardexpressprojectconguredtoserveyourskillinthe/skillroute.Combinedwithngrokthis
39 isagreattoolwhendevelopingordebugging.ro
isagreattoolwhendevelopingordebugging.routes/index.jsYoucanhandleallGETandPOSTrequestsforyouraccountlinkingprojectshere.ThemostcommononewillbethePOSTcalloftheformafterusershitthesubmitbutton.Inthisexample,wegatheruser'sinformationandcreatearowinDynamoDBfortheirinformation.ForexampleyoucangenerateanUUIDtoidentifytheusersastheprimarykeyandsenditbacktoAlexaastheaccessTokensoyoucaneasilyfetchuser'sinformationlateron. router.post(/,(req,res,next)={constmd=newMobileDetect(req.headers[user-agent]);constdb=newStorage();constemail=req.body.email;constcode=uuidV4().replace(/-/g,);constparams={id:code,email, 36Chapter7.Links VoxaDocumentation,Release2.1.0 };returndb.put(params).then(()={constredirect=${req.query.redirect_uri}#state=${req.query.state}&access_,!token=${code}&token_type=Bearer;if(md.is(AndroidOS)){console.log(redirectin
40 gandroidto:${redirect});res.redirec
gandroidto:${redirect});res.redirect(redirect);}else{console.log(redirectingwebto:${redirect});res.render(auth/success,{page:success,title:Success,redirectUrl:redirect,});}}).catch(next);}); Tonishtheauthenticationprocessyouhavetomakearedirectiontotheredirect_uriAmazonsendstoourservice.Sincetherecouldbe2originstoredirectto,wecreatethisURLdynamically;theseendpointscouldlooklikethis:https://pitangui.amazon.com/spa/skill/account-linking-status.html?vendorId=xxx-ForUnitedStatesstorehttps://layla.amazon.com/spa/skill/account-linking-status.html?vendorId=xxxxxx-ForUKandGermanystoreTheotherparameterstosendare:access_token=YOUR-TOKENtoken_type=BearerservicesJustacommonplacetoputmodelsandlibrariesuserStorage.jsUsethisleasanexampletohandledatabaselogic.SinceweuseDynamoDBforthisexample,weincluded2methods,aputand
41 aget,souser'sinformationgetstoredfromthe
aget,souser'sinformationgetstoredfromtheaccountlinkingprojectandgetfetchedfromthealexaskillside.ForreachingoutDynamoDByouneedsomepermissionsforyourlambdafunction.MakesuretograntyourlambdafunctionwitharolewithDynamoDBaccess.testYouwritetestsright?gulpleAgulprunnerconguredwithawatchtaskthatstartsyourexpressserverandlistensforchangestoreloadyourapplication. 7.14.AccountLinking37 VoxaDocumentation,Release2.1.0 serverless.ymlTheserverlessframeworkisatoolthathelpsyoumanageyourlambdaapplications,assumingyouhaveyourAWScredentialssetupproperlythisstarterkitdenestheveryminimumneededsoyoucandeployyourskilltolambdawiththefollowingcommand: $slsdeploy Runningtheproject1.Clonethevoxarepository2.Createanewskillprojectusingthesamples/starterKitdirectoryasabasis3.Makesureyou'rerunningnode4.3,thisiseasiestwithnvm4.Createaconfig/local.jsonleusingconfig/local.json.exampleasanexample5.Runtheprojectwithgulp
42 watch6.Atthispointyoushouldstartngrokhtt
watch6.Atthispointyoushouldstartngrokhttp3000andcongureyourskillintheAmazonDeveloperpaneltousethengrokhttpsendpoint. 38Chapter7.Links Index AAlexaEvent()(class),16AlexaEvent.intent.params(AlexaEvent.intentattribute),16AlexaEvent.model(AlexaEventattribute),16AlexaEvent.user(AlexaEventattribute),16audioPlayerCallback()(built-infunction),20autoLoad()(built-infunction),27Ccloudwatch()(built-infunction),26RreplaceIntent()(built-infunction),25Reply()(class),17Reply.append()(Replymethod),17Reply.toJSON()(Replymethod),17SstateFlow()(built-infunction),25Vvariable()(built-infunction),14Voxa()(class),17Voxa.execute()(Voxamethod),17Voxa.lambda()(Voxamethod),17Voxa.onAfterStateChanged()(Voxamethod),19Voxa.onAudioPlayer.PlaybackFailed()(Voxa.onAudioPlayermethod),21Voxa.onAudioPlayer.PlaybackFinished()(Voxa.onAudioPlayermethod),21Voxa.onAudioPlayer.PlaybackNearlyFinished()(Voxa.onAudioPlayermethod),21Voxa.onAud
43 ioPlayer.PlaybackStarted()(Voxa.onAudioP
ioPlayer.PlaybackStarted()(Voxa.onAudioPlayermethod),21Voxa.onAudioPlayer.PlaybackStopped()(Voxa.onAudioPlayermethod),21Voxa.onBeforeReplySent()(Voxamethod),18Voxa.onBeforeStateChanged()(Voxamethod),18Voxa.onError()(Voxamethod),20Voxa.onIntent()(Voxamethod),18Voxa.onIntentRequest()(Voxamethod),18Voxa.onLaunchRequest()(Voxamethod),18Voxa.onPlaybackController.NextCommandIssued()(Voxa.onPlaybackControllermethod),21Voxa.onPlaybackController.PauseCommandIssued()(Voxa.onPlaybackControllermethod),21Voxa.onPlaybackController.PlayCommandIssued()(Voxa.onPlaybackControllermethod),21Voxa.onPlaybackController.PreviousCommandIssued()(Voxa.onPlaybackControllermethod),21Voxa.onRequestStarted()(Voxamethod),19Voxa.onSessionEnded()(Voxamethod),19Voxa.onSessionStarted()(Voxamethod),19Voxa.onState()(Voxamethod),17Voxa.onStateMachineError()(Voxamethod),20Voxa.onSystem.ExceptionEncountered()(Voxa.onSystemmethod),19Voxa.onU