diff --git a/docs/Makefile b/docs/Makefile index 4425232e..4171faf4 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -2,7 +2,7 @@ # # You can set these variables from the command line. -SPHINXOPTS = -n -a +SPHINXOPTS = -n -a --ignore _build SPHINXBUILD = sphinx-build SPHINXPROJ = voxa SOURCEDIR = . diff --git a/docs/alexa-apis.rst b/docs/alexa-apis.rst index e6e1a934..ee7c1600 100644 --- a/docs/alexa-apis.rst +++ b/docs/alexa-apis.rst @@ -3,6 +3,9 @@ Alexa APIs ============================================== Amazon has integrated several APIs so users can leverage the Alexa's configurations, device's and user's information. + +.. _alexa-customer-contact: + -------------------------------------- Customer Contact Information Reference -------------------------------------- @@ -99,6 +102,8 @@ To send a card requesting user the permission to access their information, you c }, +.. _alexa-device-address: + ------------------------------------ Device Address Information Reference ------------------------------------ @@ -172,6 +177,8 @@ To send a card requesting user the permission to access the device address info, }, +.. _alexa-device-settings: + ------------------------- Device Settings Reference ------------------------- @@ -229,6 +236,7 @@ With Voxa, you can ask for the full device's address like this: You don't need to request to the user the permission to access the device settings info. +.. _alexa-isp: ---------------------------- In-Skill Purchases Reference @@ -312,6 +320,7 @@ When users accept or refuse to buy/cancel an item, Alexa sends a Connections.Res "token": "string" } +.. _alexa-lists: ---------------------------------------- Alexa Shopping and To-Do Lists Reference diff --git a/docs/alexa-event.rst b/docs/alexa-event.rst index 8f1fb9b3..f93756ff 100644 --- a/docs/alexa-event.rst +++ b/docs/alexa-event.rst @@ -7,24 +7,28 @@ The ``AlexaEvent`` Object The ``alexaEvent`` object contains all the information from the Voxa event, it's an object kept for the entire lifecycle of the state machine transitions and as such is a perfect place for middleware to put information that should be available on every request. - .. js:attribute:: AlexaEvent.intent - In Voxa the alexaEvent object makes ``intent.slots`` available through ``intent.params`` after aplying a simple transformation so ``{ slots: [{ name: 'Dish', value: 'Fried Chicken' }] }`` becomes ``{ Dish: 'Fried Chicken' }``, in other platforms it does it best to make the intent params for each platform also available on ``intent.params`` + .. js:attribute:: AlexaEvent.token - .. js:attribute:: AlexaEvent.user + A convenience getter to obtain the request's token, specially when using the ``Display.ElementSelected`` - A convenience getter to obtain the user from ``session.user`` or ``context.System.user`` in alexa, and ``conv.user.id`` in dialogflow. In other platforms it's also available, you can always count on the ``alexaEvent.user.userId`` being available. If there's an ``accessToken`` it will also be available through ``alexaEvent.user.accessToken`` + .. js:attribute:: AlexaEvent.alexa.customerContact - .. js:attribute:: AlexaEvent.token - A convenience getter to obtain the request's token, specially when using the ``Display.ElementSelected`` + When a customer enables your Alexa skill, your skill can request the customer's permission to the their contact information, see :ref:`alexa-customer-contact`. + + .. js:attribute:: AlexaEvent.alexa.deviceAddress + + When a customer enables your Alexa skill, your skill can obtain the customer's permission to use address data associated with the customer's Alexa device, see :ref:`alexa-device-address`. + + .. js:attribute:: AlexaEvent.alexa.deviceSettings - .. js:attribute:: AlexaEvent.requestToIntent + Alexa customers can set their timezone, distance measuring unit, and temperature measurement unit in the Alexa app, see :ref:`alexa-device-settings`. - An array of requests to be converted to intents to be used as ``app.toIntent`` in the app code. + .. js:attribute:: AlexaEvent.alexa.isp - .. js:function:: AlexaEvent.supportedInterfaces() + The `in-skill purchasing `_ feature enables you to sell premium content such as game features and interactive stories for use in skills with a custom interaction model, see :ref:`alexa-isp`. - Array of supported interfaces + .. js:attribute:: AlexaEvent.alexa.lists - :returns Array: A string array of the platform's supported interfaces + Alexa customers have access to two default lists: Alexa to-do and Alexa shopping. In addition, Alexa customer can create and manage `custom lists `_ in a skill that supports that, see :ref:`alexa-lists`. diff --git a/docs/dialogflow-directives.rst b/docs/dialogflow-directives.rst index af9bd78d..5b894653 100644 --- a/docs/dialogflow-directives.rst +++ b/docs/dialogflow-directives.rst @@ -173,11 +173,9 @@ Avoid repeating the information presented in the card in the chat bubble at all return { dialogFlowBasicCard: { text: `This is a basic card. Text in a basic card can include "quotes" and - most other unicode characters including emoji 📱. Basic cards also support + most other unicode characters including emoji. Basic cards also support some markdown formatting like *emphasis* or _italics_, **strong** or - __bold__, and ***bold itallic*** or ___strong emphasis___ as well as other - things like line \nbreaks`, // Note the two spaces before '\n' required for - // a line break to be rendered in the card. + __bold__, and ***bold itallic*** or ___strong emphasis___ `, subtitle: 'This is a subtitle', title: 'Title: this is a title', buttons: new Button({ diff --git a/docs/dialogflow-event.rst b/docs/dialogflow-event.rst index 56e6f189..54062938 100644 --- a/docs/dialogflow-event.rst +++ b/docs/dialogflow-event.rst @@ -7,20 +7,7 @@ The ``DialogFlowEvent`` Object The ``dialogFlowEvent`` object contains all the information from the Voxa event, it's an object kept for the entire lifecycle of the state machine transitions and as such is a perfect place for middleware to put information that should be available on every request. - .. js:attribute:: DialogFlowEvent.conv + .. js:attribute:: DialogFlowEvent.google.conv The conversation instance that contains the raw input sent by Dialogflow - .. js:attribute:: DialogFlowEvent.intent - - In Voxa the dialogFlowEvent object makes ``intent.slots`` available through ``intent.params`` after aplying a simple transformation so ``{ slots: [{ name: 'Dish', value: 'Fried Chicken' }] }`` becomes ``{ Dish: 'Fried Chicken' }``, in other platforms it does it best to make the intent params for each platform also available on ``intent.params`` - - .. js:attribute:: DialogFlowEvent.user - - A convenience getter to obtain the user from ``session.user`` or ``context.System.user`` in alexa, and ``conv.user.id`` in dialogflow. In other platforms it's also available, you can always count on the ``dialogFlowEvent.user.userId`` being available. If there's an ``accessToken`` it will also be available through ``dialogFlowEvent.user.accessToken`` - - .. js:function:: DialogFlowEvent.supportedInterfaces() - - Array of supported interfaces - - :returns Array: A string array of the platform's supported interfaces diff --git a/docs/index.rst b/docs/index.rst index beb8f92a..8f05478d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -122,6 +122,8 @@ Links new-alexa-user mvc-description + voxa-app + voxa-platform models views-and-variables controllers diff --git a/docs/plugins.rst b/docs/plugins.rst index b2d870dd..bb12baa5 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -13,14 +13,14 @@ After instantiating a StateMachineSkill you can register plugins on it. Built in .. code-block:: javascript 'use strict'; - const Voxa = require('voxa'); + const { VoxaApp, plugins } = require('voxa'); const Model = require('./model'); const views = require('./views'): const variables = require('./variables'); - const app = new Voxa({ Model, variables, views }); + const app = new VoxaApp({ Model, variables, views }); - Voxa.plugins.replaceIntent(app); + plugins.replaceIntent(app); State Flow plugin @@ -30,9 +30,9 @@ Stores the state transitions for every alexa event in an array. .. js:function:: stateFlow(app) - State Flow attaches callbacks to :js:func:`~Voxa.onRequestStarted`, :js:func:`~Voxa.onBeforeStateChanged` and :js:func:`~Voxa.onBeforeReplySent` to track state transitions in a ``voxaEvent.flow`` array + State Flow attaches callbacks to :js:func:`~VoxaApp.onRequestStarted`, :js:func:`~VoxaApp.onBeforeStateChanged` and :js:func:`~VoxaApp.onBeforeReplySent` to track state transitions in a ``voxaEvent.flow`` array - :param Voxa app: The app object + :param VoxaApp app: The app object @@ -58,9 +58,9 @@ It allows you to rename an intent name based on a regular expression. By default .. js:function:: replaceIntent(app, [config]) - Replace Intent plugin uses :js:func:`~Voxa.onIntentRequest` to modify the incoming request intent name + Replace Intent plugin uses :js:func:`~VoxaApp.onIntentRequest` to modify the incoming request intent name - :param Voxa app: The stateMachineSkill + :param VoxaApp app: The stateMachineSkill :param config: An object with the ``regex`` to look for and the ``replace`` value. @@ -106,9 +106,9 @@ Params .. js:function:: cloudwatch(app, cloudwatch, [eventMetric]) - CloudWatch plugin uses :js:func:`Voxa.onError`, :js:func:`Voxa.onStateMachineError` and :js:func:`Voxa.onBeforeReplySent` to log metrics + CloudWatch plugin uses :js:func:`VoxaApp.onError` and :js:func:`VoxaApp.onBeforeReplySent` to log metrics - :param Voxa app: The stateMachineSkill + :param VoxaApp app: The stateMachineSkill :param cloudwatch: A new `AWS.CloudWatch `_ object. :param putMetricDataParams: Params for `putMetricData `_ @@ -143,7 +143,7 @@ Params Autoload plugin uses ``app.onSessionStarted`` to load data the first time the user opens a skill - :param Voxa app: The stateMachineSkill. + :param VoxaApp app: The stateMachineSkill. :param config: An object with an ``adapter`` key with a `get` Promise method in which you can handle your database access to fetch information from any resource. @@ -152,6 +152,6 @@ Usage .. code-block:: javascript - const app = new Voxa({ Model, variables, views }); + const app = new VoxaApp({ Model, variables, views }); - Voxa.plugins.autoLoad(app, { adapter }); + plugins.autoLoad(app, { adapter }); diff --git a/docs/transition.rst b/docs/transition.rst index 7f6b56a5..54535272 100644 --- a/docs/transition.rst +++ b/docs/transition.rst @@ -18,29 +18,47 @@ The ``to`` key should be the name of a state in your state machine, when present ``directives`` -------------- -Directives are used passed directly to the alexa response, the format is described in `the alexa documentation `_ +Directives is an array of directive objects that implement the ``IDirective`` interface, they can make modifications to the reply object directly .. code-block:: javascript + const { PlayAudio } = require('voxa').alexa; + return { - directives: [{ - type: 'AudioPlayer.Play', - playBehavior: 'REPLACE_ALL', - audioItem: { - stream: { - token: lesson.id, - url: lesson.Url, - offsetInMilliseconds: 0, - } - } - }], + directives: [new PlayAudio(url, token)], }; + +``flow`` +-------- + +The ``flow`` key can take one of three values: + +``continue``: + This is the default value if the flow key is not present, it merely continues the state machine execution with an internal transition, it keeps building the response until a controller returns a ``yield`` or a ``terminate`` flow. + +``yield``: + This stops the state machine and returns the current response to the user without terminating the session. + +``terminate``: + This stops the state machine and returns the current response to the user, it closes the session. + + +``say`` +------- + +Used to render a view and add the result to the response + + +``reprompt`` +------------ + +Used to render a view and add the result to the response as a reprompt + + ``reply`` --------- -The ``reply`` key can take 2 forms, a simple string pointing to one of your views or a :ref:`Reply ` object. - .. code-block:: javascript return { reply: 'LaunchIntent.OpenResponse' }; diff --git a/docs/voxa-app.rst b/docs/voxa-app.rst index 4954fa7c..ec8cdbf8 100644 --- a/docs/voxa-app.rst +++ b/docs/voxa-app.rst @@ -1,9 +1,9 @@ .. _voxa-app: -Voxa +Voxa Application ================== -.. js:class:: VoxaApp.pp(config) +.. js:class:: VoxaApp(config) :param config: Configuration for your skill, it should include :ref:`views-and-variables` and optionally a :ref:`model ` and a list of appIds. @@ -13,22 +13,13 @@ Voxa const app = new VoxaApp.pp({ Model, variables, views, appIds }); -.. js:function:: VoxaApp.lambda() - - - :returns: A lambda handler that will call your :js:func:`app.execute ` method - - .. code-block:: javascript - - exports.handler = app.lambda(); - -.. js:function:: VoxaApp.execute(event) +.. js:method:: VoxaApp.execute(event, context) The main entry point for the Skill execution - :param event: The event sent by alexa. + :param event: The event sent by the platform. :param context: The context of the lambda function - :returns Promise: A response resolving to a javascript object to be sent as a result to Alexa. + :returns: Promise: A response resolving to a javascript object to be sent as a result to Alexa. .. code-block:: javascript @@ -36,7 +27,7 @@ Voxa .then(result => callback(null, result)) .catch(callback); -.. js:function:: VoxaApp.onState(stateName, handler) +.. js:method:: VoxaApp.onState(stateName, handler) Maps a handler to a state @@ -55,7 +46,7 @@ Voxa return { tell: 'LaunchIntent.OpenResponse', to: 'die' }; }); -.. js:function:: VoxaApp.onIntent(intentName, handler) +.. js:method:: VoxaApp.onIntent(intentName, handler) A shortcut for definining state controllers that map directly to an intent @@ -69,7 +60,7 @@ Voxa return { tell: 'HelpIntent.HelpAboutSkill' }; }); -.. js:function:: VoxaApp.onIntentRequest(callback, [atLast]) +.. js:method:: VoxaApp.onIntentRequest(callback, [atLast]) This is executed for all ``IntentRequest`` events, default behavior is to execute the State Machine machinery, you generally don't need to override this. @@ -77,15 +68,15 @@ Voxa :param bool last: :returns: Promise -.. js:function:: VoxaApp.onLaunchRequest(callback, [atLast]) +.. js:method:: VoxaApp.onLaunchRequest(callback, [atLast]) Adds a callback to be executed when processing a ``LaunchRequest``, the default behavior is to fake the :ref:`alexa event ` as an ``IntentRequest`` with a ``LaunchIntent`` and just defer to the ``onIntentRequest`` handlers. You generally don't need to override this. -.. js:function:: VoxaApp.onBeforeStateChanged(callback, [atLast]) +.. js:method:: VoxaApp.onBeforeStateChanged(callback, [atLast]) This is executed before entering every state, it can be used to track state changes or make changes to the :ref:`alexa event ` object -.. js:function:: VoxaApp.onBeforeReplySent(callback, [atLast]) +.. js:method:: VoxaApp.onBeforeReplySent(callback, [atLast]) Adds a callback to be executed just before sending the reply, internally this is used to add the serialized model and next state to the session. @@ -98,7 +89,7 @@ Voxa analytics.track(voxaEvent, rendered) }); -.. js:function:: VoxaApp.onAfterStateChanged(callback, [atLast]) +.. js:method:: VoxaApp.onAfterStateChanged(callback, [atLast]) Adds callbacks to be executed on the result of a state transition, this are called after every transition and internally it's used to render the :ref:`transition ` ``reply`` using the :ref:`views and variables ` @@ -115,11 +106,11 @@ Voxa }); -.. js:function:: VoxaApp.onUnhandledState(callback, [atLast]) +.. js:method:: VoxaApp.onUnhandledState(callback, [atLast]) Adds a callback to be executed when a state transition fails to generate a result, this usually happens when redirecting to a missing state or an entry call for a non configured intent, the handlers get a :ref:`alexa event ` parameter and should return a :ref:`transition ` the same as a state controller would. -.. js:function:: VoxaApp.onSessionStarted(callback, [atLast]) +.. js:method:: VoxaApp.onSessionStarted(callback, [atLast]) Adds a callback to the ``onSessinStarted`` event, this executes for all events where ``voxaEvent.session.new === true`` @@ -131,7 +122,7 @@ Voxa analytics.trackSessionStarted(voxaEvent); }); -.. js:function:: VoxaApp.onRequestStarted(callback, [atLast]) +.. js:method:: VoxaApp.onRequestStarted(callback, [atLast]) Adds a callback to be executed whenever there's a ``LaunchRequest``, ``IntentRequest`` or a ``SessionEndedRequest``, this can be used to initialize your analytics or get your account linking user data. Internally it's used to initialize the model based on the event session @@ -144,13 +135,13 @@ Voxa }); -.. js:function:: VoxaApp.onSessionEnded(callback, [atLast]) +.. js:method:: VoxaApp.onSessionEnded(callback, [atLast]) Adds a callback to the ``onSessionEnded`` event, this is called for every ``SessionEndedRequest`` or when the skill returns a transition to a state where ``isTerminal === true``, normally this is a transition to the ``die`` state. You would normally use this to track analytics -.. js:function:: VoxaApp.onSystem.ExceptionEncountered(callback, [atLast]) +.. js:method:: VoxaApp.onSystem.ExceptionEncountered(callback, [atLast]) This handles `System.ExceptionEncountered `_ event that are sent to your skill when a response to an ``AudioPlayer`` event causes an error @@ -173,7 +164,7 @@ You can register many error handlers to be used for the different kind of errors They're executed sequentially and will stop when the first handler returns a reply. -.. js:function:: VoxaApp.onStateMachineError(callback, [atLast]) +.. js:method:: VoxaApp.onStateMachineError(callback, [atLast]) This handler will catch all errors generated when trying to make transitions in the stateMachine, this could include errors in the state machine controllers, , the handlers get ``(voxaEvent, reply, error)`` parameters @@ -185,7 +176,7 @@ They're executed sequentially and will stop when the first handler returns a rep .write(); }); -.. js:function:: VoxaApp.onError(callback, [atLast]) +.. js:method:: VoxaApp.onError(callback, [atLast]) This is the more general handler and will catch all unhandled errors in the framework, it gets ``(voxaEvent, error)`` parameters as arguments @@ -203,7 +194,7 @@ Playback Controller handlers Handle events from the `AudioPlayer interface `_ -.. js:function:: audioPlayerCallback(voxaEvent, reply) +.. js:method:: audioPlayerCallback(voxaEvent, reply) All audio player middleware callbacks get a :ref:`alexa event ` and a :ref:`reply ` object @@ -228,30 +219,30 @@ Handle events from the `AudioPlayer interface `_ -.. js:function:: alexaSkillEventCallback(alexaEvent) +.. js:method:: alexaSkillEventCallback(alexaEvent) All the alexa skill event callbacks get a :ref:`alexa event ` and a :ref:`reply ` object @@ -270,22 +261,22 @@ Handle request for the `Alexa Skill Events `_ -.. js:function:: alexaListEventCallback(alexaEvent) +.. js:method:: alexaListEventCallback(alexaEvent) All the alexa list event callbacks get a :ref:`alexa event ` and a :ref:`reply ` object @@ -304,8 +295,8 @@ Handle request for the `Alexa List Events `. .. js:function:: VoxaEvent.supportedInterfaces() @@ -29,14 +44,6 @@ The ``voxaEvent`` Object :returns Array: A string array of the platform's supported interfaces - .. js:function:: VoxaEvent.mapRequestToIntent() - - Converts a request into an intent to be handled by ``app.toIntent`` - - .. js:function:: VoxaEvent.mapRequestToRequest() - - Converts a request into another type of request - ``IVoxaEvent`` is an interface that inherits its attributes and function to the specific platforms, for more information about each platform's own methods visit: - :ref:`AlexaEvent ` diff --git a/docs/voxa-platform.rst b/docs/voxa-platform.rst index e69de29b..13d75c81 100644 --- a/docs/voxa-platform.rst +++ b/docs/voxa-platform.rst @@ -0,0 +1,31 @@ +.. _voxa-platforms: + + +Voxa Platforms +================== + + +.. js:class:: VoxaPlatform(voxaApp, config) + + :param VoxaApp voxaApp: The app + :param config: The config + + +.. js:function:: VoxaPlatform.lambda() + + + :returns: A lambda handler that will call the :js:func:`app.execute ` method + + .. code-block:: javascript + + exports.handler = app.lambda(); + +.. js:function:: VoxaPlatform.lambdaHTTP() + + + :returns: A lambda handler to use with as an AWS API Gateway ProxyEvent handler that will call the :js:func:`app.execute ` method + + .. code-block:: javascript + + exports.handler = app.lambdaHTTP(); + diff --git a/hello-world/yarn.lock b/hello-world/yarn.lock index 26d025b5..bbc1288a 100644 --- a/hello-world/yarn.lock +++ b/hello-world/yarn.lock @@ -172,32 +172,10 @@ asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" -ask-sdk-core@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/ask-sdk-core/-/ask-sdk-core-2.0.7.tgz#0f2300aa99a5bd92c0bd700d1ed92ff8da1f83b9" - -ask-sdk-dynamodb-persistence-adapter@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/ask-sdk-dynamodb-persistence-adapter/-/ask-sdk-dynamodb-persistence-adapter-2.0.7.tgz#c00c88579cd32cf91b1b3d843615ff29cadc5234" - dependencies: - aws-sdk "^2.163.0" - -ask-sdk-model@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/ask-sdk-model/-/ask-sdk-model-1.3.1.tgz#a583809e4e9d3fb5308306249907808fab37fd4f" - ask-sdk-model@^1.4.0-beta.1: version "1.4.1" resolved "https://registry.yarnpkg.com/ask-sdk-model/-/ask-sdk-model-1.4.1.tgz#cbc3e071ac720336ee289dd0a57e6bbdf5c9d0f9" -ask-sdk@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/ask-sdk/-/ask-sdk-2.0.7.tgz#1157fb90399b50bb1deab55a866255fba4317d72" - dependencies: - ask-sdk-core "^2.0.7" - ask-sdk-dynamodb-persistence-adapter "^2.0.7" - ask-sdk-model "^1.0.0" - asn1@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" @@ -218,20 +196,6 @@ asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" -aws-sdk@^2.163.0: - version "2.266.1" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.266.1.tgz#1d0f14cbf82c95cec97752cd5b00df0315a67ff4" - dependencies: - buffer "4.9.1" - events "1.1.1" - ieee754 "1.1.8" - jmespath "0.15.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - uuid "3.1.0" - xml2js "0.4.17" - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -255,10 +219,6 @@ balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" -base64-js@^1.0.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" - base64url@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb" @@ -310,14 +270,6 @@ buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" -buffer@4.9.1: - version "4.9.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -411,10 +363,6 @@ escape-string-regexp@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" -events@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" - extend@^3.0.1, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" @@ -564,14 +512,6 @@ i18next@^9.0.1: version "9.1.0" resolved "https://registry.yarnpkg.com/i18next/-/i18next-9.1.0.tgz#408005fe262a990c8d93946a6de0c77bba11667b" -ieee754@1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" - -ieee754@^1.1.4: - version "1.1.12" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -591,10 +531,6 @@ is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" -isarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - isemail@1.x.x: version "1.2.0" resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a" @@ -603,10 +539,6 @@ isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" -jmespath@0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" - joi@^6.10.1: version "6.10.1" resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06" @@ -674,7 +606,7 @@ lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" -lodash@^4.0.0, lodash@^4.13.1: +lodash@^4.13.1: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" @@ -787,10 +719,6 @@ psl@^1.1.24: version "1.1.28" resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.28.tgz#4fb6ceb08a1e2214d4fd4de0ca22dae13740bc7b" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -799,10 +727,6 @@ qs@^6.5.1, qs@~6.5.1: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - request-promise-core@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6" @@ -859,14 +783,6 @@ safer-buffer@^2.0.2: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" -sax@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" - -sax@>=0.6.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - sprintf-js@^1.0.3: version "1.1.1" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c" @@ -945,17 +861,6 @@ url-join@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/url-join/-/url-join-3.0.0.tgz#26e8113ace195ea30d0fc38186e45400f9cea672" -url@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -uuid@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" - uuid@^3.1.0, uuid@^3.2.1: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" @@ -969,7 +874,7 @@ verror@1.10.0: extsprintf "^1.2.0" "voxa@file:..": - version "3.0.0-alpha29" + version "3.0.0-alpha31" dependencies: "@types/aws-lambda" "^8.10.6" "@types/bluebird" "^3.5.18" @@ -982,8 +887,6 @@ verror@1.10.0: "@types/url-join" "^0.8.2" "@types/uuid" "^3.4.3" actions-on-google "2" - ask-sdk "^2.0.7" - ask-sdk-core "^2.0.7" ask-sdk-model "^1.4.0-beta.1" azure-functions-ts-essentials "^1.3.2" bluebird "^3.5.1" @@ -1001,19 +904,6 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -xml2js@0.4.17: - version "0.4.17" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868" - dependencies: - sax ">=0.6.0" - xmlbuilder "^4.1.0" - -xmlbuilder@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5" - dependencies: - lodash "^4.0.0" - xtend@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" diff --git a/package.json b/package.json index f949684f..72f98f09 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,6 @@ "@types/url-join": "^0.8.2", "@types/uuid": "^3.4.3", "actions-on-google": "2", - "ask-sdk": "^2.0.7", - "ask-sdk-core": "^2.0.7", "ask-sdk-model": "^1.4.0-beta.1", "azure-functions-ts-essentials": "^1.3.2", "bluebird": "^3.5.1", diff --git a/run-ci.sh b/run-ci.sh index 29800632..4f831efc 100755 --- a/run-ci.sh +++ b/run-ci.sh @@ -6,7 +6,7 @@ yarn run test-ci yarn run report yarn run lint -npx typedoc --out typedoc --name Voxa --readme ./README.md --target ES5 ./src +npx typedoc --out typedoc --name Voxa --readme ./README.md --target ES5 --ignoreCompilerErrors ./src NODE_VERSION=${TRAVIS_NODE_VERSION:-} if [ ! -z ${NODE_VERSION} ]; then diff --git a/src/VoxaApp.ts b/src/VoxaApp.ts index 6f9771db..fbab53ec 100644 --- a/src/VoxaApp.ts +++ b/src/VoxaApp.ts @@ -4,26 +4,59 @@ import * as i18n from "i18next"; import * as _ from "lodash"; import { Context as AWSLambdaContext } from "aws-lambda"; -import { Ask, IDirective, IDirectiveClass, Reprompt, Say, SayP, Tell } from "./directives"; -import { InvalidTransitionError, OnSessionEndedError, TimeoutError, UnknownRequestType } from "./errors"; +import { + Ask, + IDirective, + IDirectiveClass, + Reprompt, + Say, + SayP, + Tell, +} from "./directives"; +import { + InvalidTransitionError, + OnSessionEndedError, + TimeoutError, + UnknownRequestType, +} from "./errors"; import { IModel, Model } from "./Model"; -import { IMessage, IRenderer, IRendererConfig, Renderer } from "./renderers/Renderer"; -import { isState, IState, IStateMachineConfig, isTransition, ITransition, StateMachine } from "./StateMachine"; +import { + IMessage, + IRenderer, + IRendererConfig, + Renderer, +} from "./renderers/Renderer"; +import { + isState, + IState, + IStateMachineConfig, + isTransition, + ITransition, + StateMachine, +} from "./StateMachine"; import { IBag, IVoxaEvent } from "./VoxaEvent"; import { IVoxaReply } from "./VoxaReply"; const log: debug.IDebugger = debug("voxa"); export interface IVoxaAppConfig extends IRendererConfig { - appIds?: string[]|string; + appIds?: string[] | string; Model?: IModel; RenderClass?: IRenderer; views: any; variables?: any; } -export type IEventHandler = (event: IVoxaEvent, response: IVoxaReply, transition?: ITransition) => IVoxaReply|void; -export type IErrorHandler = (event: IVoxaEvent, error: Error, ReplyClass: IVoxaReply) => IVoxaReply; +export type IEventHandler = ( + event: IVoxaEvent, + response: IVoxaReply, + transition?: ITransition, +) => IVoxaReply | void; +export type IErrorHandler = ( + event: IVoxaEvent, + error: Error, + ReplyClass: IVoxaReply, +) => IVoxaReply; export type IStateHandler = (event: IVoxaEvent) => ITransition; export class VoxaApp { @@ -47,43 +80,56 @@ export class VoxaApp { SessionEndedRequest: this.handleOnSessionEnded.bind(this), }; - _.forEach(this.requestTypes, (requestType) => this.registerRequestHandler(requestType)); + _.forEach(this.requestTypes, (requestType) => + this.registerRequestHandler(requestType), + ); this.registerEvents(); - this.onError((voxaEvent: IVoxaEvent, error: Error, reply: IVoxaReply): IVoxaReply => { - console.error("onError"); - console.error(error.message ? error.message : error); - if (error.stack) { - console.error(error.stack); - } + this.onError( + (voxaEvent: IVoxaEvent, error: Error, reply: IVoxaReply): IVoxaReply => { + console.error("onError"); + console.error(error.message ? error.message : error); + if (error.stack) { + console.error(error.stack); + } - log(error); + log(error); - reply.clear(); - reply.addStatement("An unrecoverable error occurred."); - reply.terminate(); - return reply; - }, true); + reply.clear(); + reply.addStatement("An unrecoverable error occurred."); + reply.terminate(); + return reply; + }, + true, + ); this.states = { core: {}, }; - this.config = _.assign({ - Model, - RenderClass: Renderer, - }, this.config); + this.config = _.assign( + { + Model, + RenderClass: Renderer, + }, + this.config, + ); this.validateConfig(); this.i18nextPromise = new Promise((resolve, reject) => { - this.i18n.init({ - fallbackLng: "en", - load: "all", - nonExplicitWhitelist: true, - resources: this.config.views, - }, (err: Error, t: i18n.TranslationFunction) => { - if (err) { return reject(err); } - return resolve(t); - }); + this.i18n.init( + { + fallbackLng: "en", + load: "all", + nonExplicitWhitelist: true, + resources: this.config.views, + }, + (err: Error, t: i18n.TranslationFunction) => { + if (err) { + return reject(err); + } + return resolve(t); + }, + ); }); this.renderer = new this.config.RenderClass(this.config); @@ -98,7 +144,7 @@ export class VoxaApp { this.onAfterStateChanged(this.renderDirectives); this.onBeforeReplySent(this.serializeModel, true); - this.directiveHandlers = [ Say, SayP, Ask, Reprompt, Tell ]; + this.directiveHandlers = [Say, SayP, Ask, Reprompt, Tell]; } public validateConfig() { @@ -106,7 +152,10 @@ export class VoxaApp { throw new Error("Model should have a deserialize method"); } - if (!this.config.Model.serialize && !(this.config.Model.prototype && this.config.Model.prototype.serialize)) { + if ( + !this.config.Model.serialize && + !(this.config.Model.prototype && this.config.Model.prototype.serialize) + ) { throw new Error("Model should have a serialize method"); } } @@ -114,16 +163,20 @@ export class VoxaApp { /* * This way we can simply override the method if we want different request types */ - get requestTypes(): string[] { // eslint-disable-line class-methods-use-this - return [ - "IntentRequest", - "SessionEndedRequest", - ]; + get requestTypes(): string[] { + // eslint-disable-line class-methods-use-this + return ["IntentRequest", "SessionEndedRequest"]; } - public async handleOnSessionEnded(event: IVoxaEvent, response: IVoxaReply): Promise { + public async handleOnSessionEnded( + event: IVoxaEvent, + response: IVoxaReply, + ): Promise { const sessionEndedHandlers = this.getOnSessionEndedHandlers(event.platform); - const replies = await bluebird.mapSeries(sessionEndedHandlers, (fn: IEventHandler) => fn(event, response)); + const replies = await bluebird.mapSeries( + sessionEndedHandlers, + (fn: IEventHandler) => fn(event, response), + ); const lastReply = _.last(replies); if (lastReply) { return lastReply; @@ -136,12 +189,19 @@ export class VoxaApp { * iterate on all error handlers and simply return the first one that * generates a reply */ - public async handleErrors(event: IVoxaEvent, error: Error, reply: IVoxaReply): Promise { + public async handleErrors( + event: IVoxaEvent, + error: Error, + reply: IVoxaReply, + ): Promise { const errorHandlers = this.getOnErrorHandlers(event.platform); - const replies: IVoxaReply[] = await bluebird.map(errorHandlers, async (errorHandler: IErrorHandler) => { - return await errorHandler(event, error, reply); - }); - let response: IVoxaReply|undefined = _.find(replies); + const replies: IVoxaReply[] = await bluebird.map( + errorHandlers, + async (errorHandler: IErrorHandler) => { + return await errorHandler(event, error, reply); + }, + ); + let response: IVoxaReply | undefined = _.find(replies); if (!response) { reply.clear(); response = reply; @@ -151,7 +211,10 @@ export class VoxaApp { } // Call the specific request handlers for each request type - public async execute(voxaEvent: IVoxaEvent, reply: IVoxaReply): Promise { + public async execute( + voxaEvent: IVoxaEvent, + reply: IVoxaReply, + ): Promise { log("Received new event", JSON.stringify(voxaEvent.rawEvent, null, 2)); try { // Validate that this AlexaRequest originated from authorized source. @@ -159,12 +222,23 @@ export class VoxaApp { const appId = voxaEvent.context.application.applicationId; if (_.isString(this.config.appIds) && this.config.appIds !== appId) { - log(`The applicationIds don't match: "${appId}" and "${this.config.appIds}"`); + log( + `The applicationIds don't match: "${appId}" and "${ + this.config.appIds + }"`, + ); throw new Error("Invalid applicationId"); } - if (_.isArray(this.config.appIds) && !_.includes(this.config.appIds, appId)) { - log(`The applicationIds don't match: "${appId}" and "${this.config.appIds}"`); + if ( + _.isArray(this.config.appIds) && + !_.includes(this.config.appIds, appId) + ) { + log( + `The applicationIds don't match: "${appId}" and "${ + this.config.appIds + }"`, + ); throw new Error("Invalid applicationId"); } } @@ -179,18 +253,25 @@ export class VoxaApp { case "IntentRequest": case "SessionEndedRequest": { // call all onRequestStarted callbacks serially. - const result = await bluebird.mapSeries(this.getOnRequestStartedHandlers(voxaEvent.platform), + const result = await bluebird.mapSeries( + this.getOnRequestStartedHandlers(voxaEvent.platform), (fn: IEventHandler) => { - return fn(voxaEvent, reply); - }); - - if (voxaEvent.request.type === "SessionEndedRequest" && _.get(voxaEvent, "request.reason") === "ERROR") { + return fn(voxaEvent, reply); + }, + ); + + if ( + voxaEvent.request.type === "SessionEndedRequest" && + _.get(voxaEvent, "request.reason") === "ERROR" + ) { throw new OnSessionEndedError(_.get(voxaEvent, "request.error")); } // call all onSessionStarted callbacks serially. - await bluebird.mapSeries(this.getOnSessionStartedHandlers(voxaEvent.platform), - (fn: IEventHandler) => fn(voxaEvent, reply)); + await bluebird.mapSeries( + this.getOnSessionStartedHandlers(voxaEvent.platform), + (fn: IEventHandler) => fn(voxaEvent, reply), + ); // Route the request to the proper handler which may have been overriden. return await requestHandler(voxaEvent, reply); } @@ -210,7 +291,7 @@ export class VoxaApp { promises.push(timerPromise); promises.push(executeHandlers()); - response = await bluebird.race(promises); + response = await bluebird.race(promises); if (timer) { clearTimeout(timer); } @@ -237,15 +318,24 @@ export class VoxaApp { const eventName = `on${_.upperFirst(requestType)}`; this.registerEvent(eventName); - this.requestHandlers[requestType] = async (voxaEvent: IVoxaEvent, response: IVoxaReply): Promise => { + this.requestHandlers[requestType] = async ( + voxaEvent: IVoxaEvent, + response: IVoxaReply, + ): Promise => { log(eventName); const capitalizedEventName = _.upperFirst(_.camelCase(eventName)); - const runCallback = (fn: IEventHandler): IVoxaReply => fn.call(this, voxaEvent, response); - const result = await bluebird.mapSeries(this[`get${capitalizedEventName}Handlers`](), runCallback); + const runCallback = (fn: IEventHandler): IVoxaReply => + fn.call(this, voxaEvent, response); + const result = await bluebird.mapSeries( + this[`get${capitalizedEventName}Handlers`](), + runCallback, + ); // if the handlers produced a reply we return the last one - const lastReply = _(result).filter().last(); + const lastReply = _(result) + .filter() + .last(); if (lastReply) { return lastReply; } @@ -295,21 +385,33 @@ export class VoxaApp { if (!this[eventName]) { const capitalizedEventName = _.upperFirst(_.camelCase(eventName)); - this[eventName] = (callback: IEventHandler, atLast: boolean = false, platform: string = "core") => { + this[eventName] = ( + callback: IEventHandler, + atLast: boolean = false, + platform: string = "core", + ) => { if (atLast) { - this.eventHandlers[eventName][`${platform}Last`] = this.eventHandlers[eventName][`${platform}Last`] || []; - this.eventHandlers[eventName][`${platform}Last`].push(callback.bind(this)); + this.eventHandlers[eventName][`${platform}Last`] = + this.eventHandlers[eventName][`${platform}Last`] || []; + this.eventHandlers[eventName][`${platform}Last`].push( + callback.bind(this), + ); } else { - this.eventHandlers[eventName][platform] = this.eventHandlers[eventName][platform] || []; + this.eventHandlers[eventName][platform] = + this.eventHandlers[eventName][platform] || []; this.eventHandlers[eventName][platform].push(callback.bind(this)); } }; - this[`get${capitalizedEventName}Handlers`] = (platform?: string): IEventHandler[] => { + this[`get${capitalizedEventName}Handlers`] = ( + platform?: string, + ): IEventHandler[] => { let handlers: IEventHandler[]; if (platform) { - this.eventHandlers[eventName][platform] = this.eventHandlers[eventName][platform] || []; - this.eventHandlers[eventName][`${platform}Last`] = this.eventHandlers[eventName][`${platform}Last`] || []; + this.eventHandlers[eventName][platform] = + this.eventHandlers[eventName][platform] || []; + this.eventHandlers[eventName][`${platform}Last`] = + this.eventHandlers[eventName][`${platform}Last`] || []; handlers = _.concat( this.eventHandlers[eventName].core, this.eventHandlers[eventName][platform], @@ -332,7 +434,8 @@ export class VoxaApp { stateName: string, handler: IStateHandler | ITransition, intents: string[] | string = [], - platform: string = "core"): void { + platform: string = "core", + ): void { const state = _.get(this.states[platform], stateName, { name: stateName }); const stateEnter = _.get(state, "enter", {}); @@ -342,10 +445,13 @@ export class VoxaApp { } else if (_.isString(intents)) { stateEnter[intents] = handler; } else if (_.isArray(intents)) { - _.merge(stateEnter, _(intents) - .map((intentName) => [intentName, handler]) - .fromPairs() - .value()); + _.merge( + stateEnter, + _(intents) + .map((intentName) => [intentName, handler]) + .fromPairs() + .value(), + ); } state.enter = stateEnter; _.set(this.states, [platform, stateName], state); @@ -355,7 +461,11 @@ export class VoxaApp { } } - public onIntent(intentName: string, handler: IStateHandler|ITransition, platform: string = "core"): void { + public onIntent( + intentName: string, + handler: IStateHandler | ITransition, + platform: string = "core", + ): void { if (!_.get(this.states, [platform, "entry"])) { _.set(this.states, [platform, "entry"], { to: {}, name: "entry" }); } @@ -364,29 +474,46 @@ export class VoxaApp { this.onState(intentName, handler, [], platform); } - public async runStateMachine(voxaEvent: IVoxaEvent, response: IVoxaReply): Promise { - let fromState = voxaEvent.session.new ? "entry" : _.get(voxaEvent, "session.attributes.state", "entry"); + public async runStateMachine( + voxaEvent: IVoxaEvent, + response: IVoxaReply, + ): Promise { + let fromState = voxaEvent.session.new + ? "entry" + : _.get(voxaEvent, "session.attributes.state", "entry"); if (fromState === "die") { fromState = "entry"; } const stateMachine = new StateMachine({ - onAfterStateChanged: this.getOnAfterStateChangedHandlers(voxaEvent.platform), - onBeforeStateChanged: this.getOnBeforeStateChangedHandlers(voxaEvent.platform), + onAfterStateChanged: this.getOnAfterStateChangedHandlers( + voxaEvent.platform, + ), + onBeforeStateChanged: this.getOnBeforeStateChangedHandlers( + voxaEvent.platform, + ), onUnhandledState: this.getOnUnhandledStateHandlers(voxaEvent.platform), states: this.states, }); log("Starting the state machine from %s state", fromState); - const transition: ITransition = await stateMachine.runTransition(fromState, voxaEvent, response); + const transition: ITransition = await stateMachine.runTransition( + fromState, + voxaEvent, + response, + ); if (!_.isString(transition.to) && _.get(transition, "to.isTerminal")) { await this.handleOnSessionEnded(voxaEvent, response); } - const onBeforeReplyHandlers = this.getOnBeforeReplySentHandlers(voxaEvent.platform); + const onBeforeReplyHandlers = this.getOnBeforeReplySentHandlers( + voxaEvent.platform, + ); log("Running onBeforeReplySent"); - await bluebird.mapSeries(onBeforeReplyHandlers, (fn: IEventHandler) => fn(voxaEvent, response, transition)); + await bluebird.mapSeries(onBeforeReplyHandlers, (fn: IEventHandler) => + fn(voxaEvent, response, transition), + ); return response; } @@ -394,67 +521,65 @@ export class VoxaApp { public async renderDirectives( voxaEvent: IVoxaEvent, response: IVoxaReply, - transition: ITransition): Promise { - const directives = _.concat( + transition: ITransition, + ): Promise { + const directiveClasses: IDirectiveClass[] = _.concat( _.filter(this.directiveHandlers, { platform: "core" }), _.filter(this.directiveHandlers, { platform: voxaEvent.platform }), ); - const directivesKeyOrder = _.map(directives, "key"); - - const pairs = _(transition) - .toPairs() - .sortBy((pair) => { - const [key, value] = pair; - return _.indexOf(directivesKeyOrder, key); - }) - .value(); - - while (pairs.length) { - const pair = pairs.shift(); - if (!pair) { - continue; - } - - const [key, value] = pair; - const handlers = _.filter(directives, { key }); - if (!handlers.length) { - continue; - } - - for (const handler of handlers) { - await new handler(value).writeToReply(response, voxaEvent, transition); - } - + const directivesKeyOrder = _.map(directiveClasses, "key"); + if (transition.reply) { + // special handling for `transition.reply` + const reply = await voxaEvent.t(transition.reply, { + returnObjects: true, + }); + const replyKeys = _.keys(reply); + const replyTransition = _(replyKeys) + .map((key) => { + return [key, transition.reply + "." + key]; + }) + .fromPairs() + .value(); + transition = _.merge({}, transition, replyTransition); } - if (transition.directives) { - if (_.isString(transition.directives)) { - transition.directives = await voxaEvent.renderer.renderPath(transition.directives, voxaEvent); - } - - if (!transition.directives) { - return transition; - } - - if (!_.isArray(transition.directives)) { - transition.directives = [transition.directives]; - } - - transition.directives = _.concat( - _.filter(transition.directives, (directive: any) => directive.constructor.platform === "core"), - _.filter(transition.directives, (directive: any) => directive.constructor.platform === voxaEvent.platform), - ); - - for (const handler of transition.directives) { - await handler.writeToReply(response, voxaEvent, transition); - } + transition.directives = _(transition) + .toPairs() + .sortBy((pair) => { + const [key, value] = pair; + return _.indexOf(directivesKeyOrder, key); + }) + .map( + _.spread((key, value) => { + const handlers = _.filter(directiveClasses, { key }); + return _.map( + handlers, + (Directive: IDirectiveClass) => new Directive(value), + ); + }), + ) + .flatten() + .concat(transition.directives) + .filter() + .filter((directive) => { + const constructor: any = directive.constructor; + return _.includes(["core", voxaEvent.platform], constructor.platform); + }) + .value(); + + for (const handler of transition.directives) { + await handler.writeToReply(response, voxaEvent, transition); } return transition; } - public async serializeModel(voxaEvent: IVoxaEvent, response: IVoxaReply, transition: ITransition): Promise { + public async serializeModel( + voxaEvent: IVoxaEvent, + response: IVoxaReply, + transition: ITransition, + ): Promise { const serialize = _.get(voxaEvent, "model.serialize"); // we do require models to have a serialize method and check that when Voxa is initialized, @@ -468,10 +593,18 @@ export class VoxaApp { if (!transition.to) { throw new InvalidTransitionError(transition, "Missing transition.to"); } - const stateName = typeof transition.to === "string" ? transition.to - : isState(transition.to) ? transition.to.name - : ""; - if (!stateName) { throw new InvalidTransitionError(transition, "Expected transition to transition to something"); } + const stateName = + typeof transition.to === "string" + ? transition.to + : isState(transition.to) + ? transition.to.name + : ""; + if (!stateName) { + throw new InvalidTransitionError( + transition, + "Expected transition to transition to something", + ); + } // We save off the state so that we know where to resume from when the conversation resumes const modelData = await voxaEvent.model.serialize(); @@ -483,7 +616,7 @@ export class VoxaApp { await response.saveSession(attributes, voxaEvent); } - public async transformRequest(voxaEvent: IVoxaEvent): Promise { + public async transformRequest(voxaEvent: IVoxaEvent): Promise { await this.i18nextPromise; let model: Model; const data = voxaEvent.session.attributes.model as IBag; @@ -493,7 +626,7 @@ export class VoxaApp { model = await Model.deserialize(data, voxaEvent); } - model.state = (model.state === "die") ? "entry" : model.state; + model.state = model.state === "die" ? "entry" : model.state; voxaEvent.model = model; log("Initialized model like %s", JSON.stringify(voxaEvent.model)); @@ -502,12 +635,14 @@ export class VoxaApp { } } -export function timeout(context: AWSLambdaContext): { timerPromise: Promise, timer: NodeJS.Timer|undefined } { +export function timeout( + context: AWSLambdaContext, +): { timerPromise: Promise; timer: NodeJS.Timer | undefined } { const timeRemaining = context.getRemainingTimeInMillis(); - let timer: NodeJS.Timer|undefined; - const timerPromise = new Promise((resolve, reject) => { - timer = setTimeout( () => { + let timer: NodeJS.Timer | undefined; + const timerPromise = new Promise((resolve, reject) => { + timer = setTimeout(() => { reject(new TimeoutError()); }, Math.max(timeRemaining - 500, 0)); }); diff --git a/src/VoxaEvent.ts b/src/VoxaEvent.ts index 328014b0..ec9de0a6 100644 --- a/src/VoxaEvent.ts +++ b/src/VoxaEvent.ts @@ -3,14 +3,15 @@ import * as i18n from "i18next"; import * as _ from "lodash"; -import { Model} from "./Model"; +import { Model } from "./Model"; import { Renderer } from "./renderers/Renderer"; -export interface ITypeMap { +export interface ITypeMap { [x: string]: string; } export abstract class IVoxaEvent { + public abstract get supportedInterfaces(): string[]; public executionContext: any; // this would a lambda or azure function context public rawEvent: any; // the raw event as sent by the service public session!: IVoxaSession; @@ -30,9 +31,18 @@ export abstract class IVoxaEvent { this.executionContext = context; } - public abstract get supportedInterfaces(): string[]; + protected mapRequestToRequest(): void { + const requestType = this.request.type; + const newRequestType = this.requestToRequest[requestType]; + + if (!newRequestType) { + return; + } + + _.set(this, "request.type", newRequestType); + } - public mapRequestToIntent(): void { + protected mapRequestToIntent(): void { const requestType = this.request.type; const intentName = this.requestToIntent[requestType]; @@ -46,18 +56,6 @@ export abstract class IVoxaEvent { }); _.set(this, "request.type", "IntentRequest"); } - - public mapRequestToRequest(): void { - const requestType = this.request.type; - const newRequestType = this.requestToRequest[requestType]; - - if (!newRequestType) { - return; - } - - _.set(this, "request.type", newRequestType); - } - } export interface IVoxaUser { diff --git a/src/directives.ts b/src/directives.ts index 5b165766..a7b8609a 100644 --- a/src/directives.ts +++ b/src/directives.ts @@ -10,6 +10,7 @@ import * as bluebird from "bluebird"; import * as _ from "lodash"; import { ITransition } from "./StateMachine"; +import { VoxaApp } from "./VoxaApp"; import { IVoxaEvent } from "./VoxaEvent"; import { IVoxaReply } from "./VoxaReply"; @@ -17,68 +18,33 @@ export interface IDirectiveClass { platform: string; // botframework, dialogFlow or alexa key: string; // The key in the transition that links to the specific directive - new(...args: any[]): IDirective; + new (...args: any[]): IDirective; } export interface IDirective { - writeToReply: (reply: IVoxaReply, event: IVoxaEvent, transition: ITransition) => Promise; -} - -export class Reply implements IDirective { - public static key: string = "reply"; - public static platform: string = "core"; - - constructor(public viewPaths: string|string[]) {} - - public async writeToReply(reply: IVoxaReply, event: IVoxaEvent, transition: ITransition): Promise { - let viewPaths = this.viewPaths; - if (!_.isArray(viewPaths)) { - viewPaths = [viewPaths]; - } - - await bluebird.map(viewPaths, async (viewPath: string): Promise => { - const message = await event.renderer.renderPath(viewPath, event); - if (message.say) { - await new SayP(message.say).writeToReply(reply, event, transition); - } - - if (message.ask) { - reply.addStatement(message.ask); - transition.flow = "yield"; - } - - if (message.tell) { - reply.addStatement(message.tell); - reply.terminate(); - transition.flow = "terminate"; - } - - if (message.reprompt) { - reply.addReprompt(message.reprompt); - } - - if (message.directives) { - if (transition.directives) { - transition.directives = transition.directives.concat(message.directives); - } else { - transition.directives = message.directives; - } - } - }); - } + writeToReply: ( + reply: IVoxaReply, + event: IVoxaEvent, + transition: ITransition, + voxaApp: VoxaApp, + ) => Promise; } export class Reprompt implements IDirective { public static key: string = "reprompt"; public static platform: string = "core"; - public viewPath: string; - constructor(viewPath: string) { - this.viewPath = viewPath; - } + constructor(public viewPath: string) {} - public async writeToReply(reply: IVoxaReply, event: IVoxaEvent, transition: ITransition): Promise { - const statement = await event.renderer.renderPath(this.viewPath, event); + public async writeToReply( + reply: IVoxaReply, + event: IVoxaEvent, + transition: ITransition, + ): Promise { + let statement = await event.renderer.renderPath(this.viewPath, event); + if (_.isArray(statement)) { + statement = _.sample(statement); + } reply.addReprompt(statement); } } @@ -88,19 +54,34 @@ export class Ask implements IDirective { public static platform: string = "core"; public viewPaths: string[]; - constructor(viewPaths: string|string[]) { + constructor(viewPaths: string | string[]) { this.viewPaths = _.isString(viewPaths) ? [viewPaths] : viewPaths; } - public async writeToReply(reply: IVoxaReply, event: IVoxaEvent, transition: ITransition): Promise { + public async writeToReply( + reply: IVoxaReply, + event: IVoxaEvent, + transition: ITransition, + ): Promise { for (const viewPath of this.viewPaths) { const statement = await event.renderer.renderPath(viewPath, event); - if (_.isString(statement)) { + if (_.isArray(statement)) { + reply.addStatement(_.sample(statement)); + } else if (_.isString(statement)) { reply.addStatement(statement); } else if (statement.ask) { - reply.addStatement(statement.ask); + if (_.isArray(statement.ask)) { + reply.addStatement(_.sample(statement.ask)); + } else { + reply.addStatement(statement.ask); + } + if (statement.reprompt) { - reply.addReprompt(statement.reprompt); + if (_.isArray(statement.reprompt)) { + reply.addReprompt(_.sample(statement.reprompt)); + } else { + reply.addReprompt(statement.reprompt); + } } } } @@ -114,16 +95,23 @@ export class Say implements IDirective { public static key: string = "say"; public static platform: string = "core"; - constructor(public viewPaths: string|string[]) { } + constructor(public viewPaths: string | string[]) {} - public async writeToReply(reply: IVoxaReply, event: IVoxaEvent, transition: ITransition): Promise { + public async writeToReply( + reply: IVoxaReply, + event: IVoxaEvent, + transition: ITransition, + ): Promise { let viewPaths = this.viewPaths; if (_.isString(viewPaths)) { viewPaths = [viewPaths]; } await bluebird.mapSeries(viewPaths, async (view: string) => { - const statement = await event.renderer.renderPath(view, event); + let statement = await event.renderer.renderPath(view, event); + if (_.isArray(statement)) { + statement = _.sample(statement); + } reply.addStatement(statement); }); } @@ -133,29 +121,32 @@ export class SayP implements IDirective { public static key: string = "sayp"; public static platform: string = "core"; - constructor(public statements: string|string[]) { } + constructor(public statement: string) {} - public async writeToReply(reply: IVoxaReply, event: IVoxaEvent, transition: ITransition): Promise { - let statements = this.statements; - if (_.isString(statements)) { - statements = [statements]; - } - - _.map(statements, (s) => reply.addStatement(s)); + public async writeToReply( + reply: IVoxaReply, + event: IVoxaEvent, + transition: ITransition, + ): Promise { + reply.addStatement(this.statement); } } export class Tell implements IDirective { public static key: string = "tell"; public static platform: string = "core"; - public viewPath: string; - constructor(viewPath: string) { - this.viewPath = viewPath; - } + constructor(public viewPath: string) {} - public async writeToReply(reply: IVoxaReply, event: IVoxaEvent, transition: ITransition): Promise { - const statement = await event.renderer.renderPath(this.viewPath, event); + public async writeToReply( + reply: IVoxaReply, + event: IVoxaEvent, + transition: ITransition, + ): Promise { + let statement = await event.renderer.renderPath(this.viewPath, event); + if (_.isArray(statement)) { + statement = _.sample(statement); + } reply.addStatement(statement); reply.terminate(); transition.flow = "terminate"; diff --git a/src/platforms/dialog-flow/DialogFlowEvent.ts b/src/platforms/dialog-flow/DialogFlowEvent.ts index a4db44f0..003f86b5 100644 --- a/src/platforms/dialog-flow/DialogFlowEvent.ts +++ b/src/platforms/dialog-flow/DialogFlowEvent.ts @@ -1,8 +1,16 @@ -import { DialogflowConversation, GoogleCloudDialogflowV2WebhookRequest } from "actions-on-google"; +import { + DialogflowConversation, + GoogleCloudDialogflowV2WebhookRequest, +} from "actions-on-google"; import { TranslationFunction } from "i18next"; import * as _ from "lodash"; import { Model } from "../../Model"; -import { IVoxaEvent, IVoxaIntent, IVoxaSession, IVoxaUser } from "../../VoxaEvent"; +import { + IVoxaEvent, + IVoxaIntent, + IVoxaSession, + IVoxaUser, +} from "../../VoxaEvent"; import { DialogFlowIntent } from "./DialogFlowIntent"; import { DialogFlowSession } from "./DialogFlowSession"; @@ -15,35 +23,40 @@ export class DialogFlowEvent extends IVoxaEvent { public platform: string; public context: any; public intent: DialogFlowIntent; - public conv: DialogflowConversation; + public google: { conv: DialogflowConversation }; constructor(event: GoogleCloudDialogflowV2WebhookRequest, context: any) { super(event, context); - this.conv = new DialogflowConversation({ - body: event, - headers: {}, - }); + this.google = { + conv: new DialogflowConversation({ + body: event, + headers: {}, + }), + }; this.request = { - locale: _.get(event.queryResult, "languageCode"), - type: "IntentRequest", + locale: _.get(event.queryResult, "languageCode"), + type: "IntentRequest", }; - this.session = new DialogFlowSession(this.conv); - this.intent = new DialogFlowIntent(this.conv); + this.session = new DialogFlowSession(this.google.conv); + this.intent = new DialogFlowIntent(this.google.conv); this.platform = "dialogFlow"; } get user(): IVoxaUser { - const response = { - accessToken: this.conv.user.access.token, - userId: this.conv.user.id, + const response = { + accessToken: this.google.conv.user.access.token, + userId: this.google.conv.user.id, }; - return _.merge({}, this.conv.user, response); + return _.merge({}, this.google.conv.user, response); } get supportedInterfaces(): string[] { - let capabilities = _.map(this.conv.surface.capabilities.list, "name"); + let capabilities = _.map( + this.google.conv.surface.capabilities.list, + "name", + ); capabilities = _.filter(capabilities); return capabilities as string[]; diff --git a/src/platforms/dialog-flow/DialogFlowReply.ts b/src/platforms/dialog-flow/DialogFlowReply.ts index 52c9a1c7..9057cad4 100644 --- a/src/platforms/dialog-flow/DialogFlowReply.ts +++ b/src/platforms/dialog-flow/DialogFlowReply.ts @@ -31,7 +31,7 @@ export class DialogFlowReply implements IVoxaReply { constructor() { this.payload = { - google : { + google: { expectUserResponse: true, isSsml: true, }, @@ -41,9 +41,11 @@ export class DialogFlowReply implements IVoxaReply { public async saveSession(attributes: IBag, event: IVoxaEvent): Promise { const dialogFlowEvent = event as DialogFlowEvent; const serializedData = JSON.stringify(attributes); - dialogFlowEvent.conv.contexts.set("attributes", 10000, { attributes: serializedData }); + dialogFlowEvent.google.conv.contexts.set("attributes", 10000, { + attributes: serializedData, + }); - this.outputContexts = dialogFlowEvent.conv.contexts._serialize(); + this.outputContexts = dialogFlowEvent.google.conv.contexts._serialize(); } public get speech() { @@ -92,5 +94,4 @@ export class DialogFlowReply implements IVoxaReply { this.payload.google.noInputPrompts = noInputPrompts; } - } diff --git a/src/renderers/Renderer.ts b/src/renderers/Renderer.ts index 407e1a89..8c745d42 100644 --- a/src/renderers/Renderer.ts +++ b/src/renderers/Renderer.ts @@ -57,12 +57,7 @@ export class Renderer { throw new Error(`View ${view} for ${locale} locale is missing`); } - const statement = await this.renderMessage(message, voxaEvent); - if (_.isArray(statement)) { - return _.sample(statement); - } - - return statement; + return this.renderMessage(message, voxaEvent); } public renderMessage(msg: any, event: IVoxaEvent) { @@ -83,7 +78,7 @@ export class Renderer { .map(_.spread((key, value) => { const isAnOpenResponse = _.includes(["ask", "tell", "say", "reprompt"], key); if (isAnOpenResponse && _.isArray(value)) { - return [key, deepSearchRenderVariable(_.sample(value), voxaEvent)]; + return [key, deepSearchRenderVariable(value, voxaEvent)]; } return [key, deepSearchRenderVariable(value, voxaEvent)]; diff --git a/test/Renderer.spec.ts b/test/Renderer.spec.ts index d37b8844..b83d6caf 100644 --- a/test/Renderer.spec.ts +++ b/test/Renderer.spec.ts @@ -23,12 +23,11 @@ describe("Renderer", () => { let renderer: Renderer; before(() => { - i18n - .init({ - load: "all", - nonExplicitWhitelist: true, - resources: views, - }); + i18n.init({ + load: "all", + nonExplicitWhitelist: true, + resources: views, + }); }); beforeEach(() => { @@ -48,14 +47,12 @@ describe("Renderer", () => { "de-DE": { number: "ein", question: "wie spät ist es?", - random: ["zufällig1", "zufällig2", "zufällig3", "zufällig4", "zufällig5"], say: "sagen\nwie spät ist es?", site: "Ok für weitere Infos besuchen example.com Website", }, "en-US": { number: "one", question: "What time is it?", - random: ["Random 1", "Random 2", "Random 3", "Random 4"], say: "say\nWhat time is it?", site: "Ok. For more info visit example.com site.", }, @@ -69,7 +66,9 @@ describe("Renderer", () => { const reply = await skill.execute(event, new AlexaReply()); // expect(reply.error.message).to.equal(`View Number.One for ${localeMissing} locale is missing`); - expect(reply.speech).to.equal("An unrecoverable error occurred."); + expect(reply.speech).to.equal( + "An unrecoverable error occurred.", + ); }); _.forEach(locales, (translations, locale) => { @@ -80,34 +79,43 @@ describe("Renderer", () => { skill = new VoxaApp({ variables, views }); }); - it(`shold return a random response from the views array for ${locale}`, async () => { - skill.onIntent("SomeIntent", () => ({ ask: "RandomResponse" })); - event.request.locale = locale; - const reply = await skill.execute(event, new AlexaReply()); - expect(reply.speech).to.not.be.undefined; - expect(reply.speech).to.be.oneOf(_.map(translations.random, (tr) => `${tr}`)); - }); - it(`should return the correct translation for ${locale}`, async () => { - _.map(statesDefinition, (state, name: string) => skill.onState(name, state)); + _.map(statesDefinition, (state, name: string) => + skill.onState(name, state), + ); event.request.locale = locale; - const reply = await skill.execute(event, new AlexaReply()) as AlexaReply; + const reply = (await skill.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.speech).to.equal(`${translations.site}`); expect(reply.response.directives).to.be.undefined; }); it(`work with array responses ${locale}`, async () => { - skill.onIntent("SomeIntent", () => ({ say: "Say.Say", ask: "Question.Ask", to: "entry" })); + skill.onIntent("SomeIntent", () => ({ + ask: "Ask", + say: "Say", + to: "entry", + })); event.request.locale = locale; - const reply = await skill.execute(event, new AlexaReply()) as AlexaReply; - expect(reply.speech).to.deep.equal(`${translations.say}`); + const reply = (await skill.execute( + event, + new AlexaReply(), + )) as AlexaReply; + expect(reply.speech).to.deep.equal( + `${translations.say}`, + ); expect(reply.response.directives).to.be.undefined; }); it("should have the locale available in variables", async () => { skill.onIntent("SomeIntent", () => ({ tell: "Number.One" })); event.request.locale = locale; - const reply = await skill.execute(event, new AlexaReply()) as AlexaReply; + const reply = (await skill.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.speech).to.equal(`${translations.number}`); expect(reply.response.directives).to.be.undefined; }); @@ -115,53 +123,68 @@ describe("Renderer", () => { it("should return response with directives", async () => { const playAudio = new PlayAudio("url", "123", 0); - skill.onIntent("SomeIntent", () => ({ ask: "Question.Ask", to: "entry", directives: [playAudio] })); + skill.onIntent("SomeIntent", () => ({ + ask: "Ask", + directives: [playAudio], + to: "entry", + })); event.request.locale = locale; - const reply = await skill.execute(event, new AlexaReply()) as AlexaReply; - expect(reply.speech).to.equal(`${translations.question}`); + const reply = (await skill.execute( + event, + new AlexaReply(), + )) as AlexaReply; + expect(reply.speech).to.equal( + `${translations.question}`, + ); expect(reply.response.directives).to.be.ok; }); }); }); it("should render the correct view based on path", async () => { - const rendered = await renderer.renderPath("Question.Ask", event); - expect(rendered).to.deep.equal({ ask: "What time is it?", reprompt: "What time is it?" }); + const rendered = await renderer.renderPath("Ask", event); + expect(rendered).to.deep.equal({ + ask: "What time is it?", + reprompt: "What time is it?", + }); }); it("should use the passed variables and model", async () => { event.model = new Model(); - event.model.count = 1; - const rendered = await renderer.renderMessage({ say: "{count}" }, event); + event.model.count = 1; + const rendered = await renderer.renderMessage({ say: "{count}" }, event); expect(rendered).to.deep.equal({ say: "1" }); }); it("should fail for missing variables", (done) => { - renderer.renderMessage({ say: "{missing}" }, event) + renderer + .renderMessage({ say: "{missing}" }, event) .then(() => done("Should have failed")) .catch((error) => { expect(error.message).to.equal("No such variable in views, missing"); done(); }); - }); it("should throw an exception if path doesn't exists", (done) => { - renderer.renderPath("Missing.Path", event).then(() => done("Should have thrown"), (error) => { - expect(error.message).to.equal("View Missing.Path for en-US locale is missing"); - done(); - }); - }); - - it("should select a random option from the samples", async () => { - const rendered = await renderer.renderPath("RandomResponse", event); - expect(rendered).to.be.oneOf(["Random 1", "Random 2", "Random 3", "Random 4"]); + renderer.renderPath("Missing.Path", event).then( + () => done("Should have thrown"), + (error) => { + expect(error.message).to.equal( + "View Missing.Path for en-US locale is missing", + ); + done(); + }, + ); }); it("should use deeply search to render object variable", async () => { event.model = new Model(); - event.model.count = 1; - const rendered = await renderer.renderMessage({ card: "{exitCard}", number: 1 }, event); + event.model.count = 1; + const rendered = await renderer.renderMessage( + { card: "{exitCard}", number: 1 }, + event, + ); expect(rendered).to.deep.equal({ card: { @@ -179,10 +202,13 @@ describe("Renderer", () => { it("should use deeply search variable and model in complex object structure", async () => { event.model = new Model(); - event.model.count = 1; - const rendered = await renderer.renderMessage({ - card: { title: "{count}", text: "{count}", array: [{ a: "{count}" }] }, - }, event); + event.model.count = 1; + const rendered = await renderer.renderMessage( + { + card: { title: "{count}", text: "{count}", array: [{ a: "{count}" }] }, + }, + event, + ); expect(rendered).to.deep.equal({ card: { @@ -199,9 +225,15 @@ describe("Renderer", () => { }); it("should use the dialogFlow view if available", async () => { - const dialogFlowEvent = new DialogFlowEvent(require("./requests/dialog-flow/launchIntent.json"), {}); + const dialogFlowEvent = new DialogFlowEvent( + require("./requests/dialog-flow/launchIntent.json"), + {}, + ); dialogFlowEvent.t = event.t; - const rendered = await renderer.renderPath("LaunchIntent.OpenResponse", dialogFlowEvent); + const rendered = await renderer.renderPath( + "LaunchIntent.OpenResponse", + dialogFlowEvent, + ); expect(rendered).to.equal("Hello from DialogFlow"); }); }); diff --git a/test/VoxaApp.spec.ts b/test/VoxaApp.spec.ts index 58fb08c1..64bc82d6 100644 --- a/test/VoxaApp.spec.ts +++ b/test/VoxaApp.spec.ts @@ -1,8 +1,8 @@ import "mocha"; -import { canfulfill } from "ask-sdk-model"; +import { canfulfill, RequestEnvelope } from "ask-sdk-model"; import { expect, use } from "chai"; -import * as _ from "lodash"; +import * as _ from "lodash"; import * as simple from "simple-mock"; import { Model } from "../src/Model"; @@ -21,12 +21,11 @@ const rb = new AlexaRequestBuilder(); describe("VoxaApp", () => { let statesDefinition: any; - let event: any; + let event: AlexaEvent; beforeEach(() => { event = new AlexaEvent(rb.getIntentRequest("SomeIntent")); - simple.mock(AlexaPlatform, "apiRequest") - .resolveWith(true); + simple.mock(AlexaPlatform, "apiRequest").resolveWith(true); statesDefinition = { DisplayElementSelected: { tell: "ExitIntent.Farewell", to: "die" }, @@ -51,7 +50,9 @@ describe("VoxaApp", () => { const reply = await voxaApp.execute(event, new AlexaReply()); // expect(reply.error).to.be.undefined; - expect(reply.speech).to.deep.equal("Ok. For more info visit example.com site."); + expect(reply.speech).to.deep.equal( + "Ok. For more info visit example.com site.", + ); }); }); @@ -92,7 +93,10 @@ describe("VoxaApp", () => { const stateFn = simple.stub(); const stateFn2 = simple.stub(); - voxaApp.onState("init", stateFn, ["AMAZON.NoIntent", "AMAZON.StopIntent"]); + voxaApp.onState("init", stateFn, [ + "AMAZON.NoIntent", + "AMAZON.StopIntent", + ]); voxaApp.onState("init", stateFn2, "AMAZON.YesIntent"); expect(voxaApp.states.core.init).to.deep.equal({ @@ -115,7 +119,10 @@ describe("VoxaApp", () => { voxaApp.onState("secondState", () => ({})); event = new AlexaEvent(rb.getIntentRequest("LaunchIntent")); - const reply = await voxaApp.execute(event, new AlexaReply()) as AlexaReply; + const reply = (await voxaApp.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.sessionAttributes.state).to.equal("secondState"); expect(reply.response.shouldEndSession).to.be.false; }); @@ -129,16 +136,22 @@ describe("VoxaApp", () => { voxaApp.onState("secondState", () => ({})); event = new AlexaEvent(rb.getIntentRequest("LaunchIntent")); - const reply = await voxaApp.execute(event, new AlexaReply()) as AlexaReply; + const reply = (await voxaApp.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.sessionAttributes.foo).to.equal("bar"); }); it("should add the message key from the transition to the reply", async () => { const voxaApp = new VoxaApp({ variables, views }); - voxaApp.onIntent("LaunchIntent", () => ({ sayp: "This is my message"})); + voxaApp.onIntent("LaunchIntent", () => ({ sayp: "This is my message" })); event.intent.name = "LaunchIntent"; - const reply = await voxaApp.execute(event, new AlexaReply()) as AlexaReply; + const reply = (await voxaApp.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.speech).to.deep.equal("This is my message"); }); @@ -147,9 +160,14 @@ describe("VoxaApp", () => { voxaApp.onIntent("LaunchIntent", () => ({ ask: "Missing.View" })); event.intent.name = "LaunchIntent"; - const reply = await voxaApp.execute(event, new AlexaReply()) as AlexaReply; + const reply = (await voxaApp.execute( + event, + new AlexaReply(), + )) as AlexaReply; // expect(reply.error).to.be.an("error"); - expect(reply.speech).to.equal("An unrecoverable error occurred."); + expect(reply.speech).to.equal( + "An unrecoverable error occurred.", + ); }); it("should allow multiple reply paths in reply key", async () => { @@ -160,32 +178,28 @@ describe("VoxaApp", () => { }); event.intent.name = "LaunchIntent"; - const reply = await voxaApp.execute(event, new AlexaReply()) as AlexaReply; + const reply = (await voxaApp.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.speech).to.deep.equal("0\n0"); }); it("should display element selected request", async () => { - const stateMachineSkill = new VoxaApp({ variables, views }); - stateMachineSkill.onIntent("Display.ElementSelected", { to: "die", tell: "ExitIntent.Farewell" }); - event.intent = undefined; - event.request.type = "Display.ElementSelected"; + const voxaApp = new VoxaApp({ variables, views }); + const alexaEvent = rb.getDisplayElementSelectedRequest("token"); + voxaApp.onIntent("Display.ElementSelected", { + tell: "ExitIntent.Farewell", + to: "die", + }); - const reply = await new AlexaPlatform(stateMachineSkill).execute(event, new AlexaReply()) as AlexaReply; - expect(reply.speech).to.equal("Ok. For more info visit example.com site."); + const alexaSkill = new AlexaPlatform(voxaApp); + const reply = await alexaSkill.execute(alexaEvent, {}); + expect(_.get(reply, "response.outputSpeech.ssml")).to.equal( + "Ok. For more info visit example.com site.", + ); }); - // it("should throw an error if multiple replies include anything after say or tell", async () => { - // const voxaApp = new VoxaApp({ variables, views }); - // voxaApp.onIntent("LaunchIntent", (voxaEvent) => { - // voxaEvent.model.count = 0; - // return { tell: ["Count.Tell", "Count.Say"] }; - // }); - // event.intent.name = "LaunchIntent"; - - // const reply = await voxaApp.execute(event, new AlexaReply()) as AlexaReply; - // expect(reply.speech).to.equal("Can't append to already yielding response"); - // }); - it("should be able to just pass through some intents to states", async () => { const voxaApp = new VoxaApp({ variables, views }); let called = false; @@ -196,7 +210,9 @@ describe("VoxaApp", () => { const alexa = new AlexaPlatform(voxaApp); - const loopOffEvent = new AlexaEvent(rb.getIntentRequest("AMAZON.LoopOffIntent")); + const loopOffEvent = new AlexaEvent( + rb.getIntentRequest("AMAZON.LoopOffIntent"), + ); await alexa.execute(loopOffEvent, AlexaReply); expect(called).to.be.true; @@ -209,11 +225,13 @@ describe("VoxaApp", () => { it("should call the entry state on a new session", async () => { statesDefinition.entry = simple.stub().resolveWith({ - reply: "ExitIntent.Farewell", + say: "ExitIntent.Farewell", }); const voxaApp = new VoxaApp({ variables, views }); - _.map(statesDefinition, (state: any, name: string) => voxaApp.onState(name, state)); + _.map(statesDefinition, (state: any, name: string) => + voxaApp.onState(name, state), + ); await voxaApp.execute(event, new AlexaReply()); expect(statesDefinition.entry.called).to.be.true; @@ -228,29 +246,32 @@ describe("VoxaApp", () => { return { tell: "ExitIntent.Farewell", to: "die" }; }); - _.map(statesDefinition, (state: any, name: string) => voxaApp.onState(name, state)); + _.map(statesDefinition, (state: any, name: string) => + voxaApp.onState(name, state), + ); await voxaApp.execute(event, new AlexaReply()); expect(statesDefinition.entry.called).to.be.true; expect(statesDefinition.entry.lastCall.threw).to.be.not.ok; }); // it("should simply set an empty session if serialize is missing", async () => { - // const voxaApp = new VoxaApp({ views, variables }); - // statesDefinition.entry = simple.spy((request) => { - // request.model = null; - // return { ask: "Question.Ask", to: "initState" }; - // }); - // _.map(statesDefinition, (state: any, name: string) => voxaApp.onState(name, state)); - // const reply = await voxaApp.execute(event, new AlexaReply()) as AlexaReply; - // // expect(reply.error).to.be.undefined; - // expect(statesDefinition.entry.called).to.be.true; - // expect(statesDefinition.entry.lastCall.threw).to.be.not.ok; - // expect(reply.sessionAttributes).to.deep.equal(new Model({ state: "initState" })); + // const voxaApp = new VoxaApp({ views, variables }); + // statesDefinition.entry = simple.spy((request) => { + // request.model = null; + // return { ask: "Ask", to: "initState" }; + // }); + // _.map(statesDefinition, (state: any, name: string) => voxaApp.onState(name, state)); + // const reply = await voxaApp.execute(event, new AlexaReply()) as AlexaReply; + // // expect(reply.error).to.be.undefined; + // expect(statesDefinition.entry.called).to.be.true; + // expect(statesDefinition.entry.lastCall.threw).to.be.not.ok; + // expect(reply.sessionAttributes).to.deep.equal(new Model({ state: "initState" })); // }); it("should allow async serialization in Model", async () => { class PromisyModel extends Model { - public serialize() { // eslint-disable-line class-methods-use-this + public serialize() { + // eslint-disable-line class-methods-use-this return Promise.resolve({ value: 1, }); @@ -261,15 +282,20 @@ describe("VoxaApp", () => { statesDefinition.entry = simple.spy((request) => { expect(request.model).to.not.be.undefined; expect(request.model).to.be.an.instanceOf(PromisyModel); - return { ask: "Question.Ask", to: "initState" }; + return { ask: "Ask", to: "initState" }; }); - _.map(statesDefinition, (state: any, name: string) => voxaApp.onState(name, state)); + _.map(statesDefinition, (state: any, name: string) => + voxaApp.onState(name, state), + ); const platform = new AlexaPlatform(voxaApp); - const reply = await platform.execute(event, {}) as AlexaReply; + const reply = (await platform.execute(event, {})) as AlexaReply; expect(statesDefinition.entry.called).to.be.true; expect(statesDefinition.entry.lastCall.threw).to.be.not.ok; - expect(reply.sessionAttributes).to.deep.equal({ model: { value: 1 }, state: "initState"}); + expect(reply.sessionAttributes).to.deep.equal({ + model: { value: 1 }, + state: "initState", + }); }); it("should let model.deserialize return a Promise", async () => { @@ -286,19 +312,23 @@ describe("VoxaApp", () => { expect(request.model).to.not.be.undefined; expect(request.model).to.be.an.instanceOf(PromisyModel); expect(request.model.didDeserialize).to.eql("yep"); - return { reply: "ExitIntent.Farewell", to: "die" }; + return { say: "ExitIntent.Farewell", to: "die" }; }); - _.map(statesDefinition, (state: any, name: string) => voxaApp.onState(name, state)); - event.session.attributes = {model: {foo: "bar"}}; - await voxaApp.execute(event, new AlexaReply()) ; + _.map(statesDefinition, (state: any, name: string) => + voxaApp.onState(name, state), + ); + event.session.attributes = { model: { foo: "bar" } }; + await voxaApp.execute(event, new AlexaReply()); expect(statesDefinition.entry.called).to.be.true; expect(statesDefinition.entry.lastCall.threw).to.be.not.ok; }); it("should call onSessionEnded callbacks if state is die", async () => { const voxaApp = new VoxaApp({ Model, views, variables }); - _.map(statesDefinition, (state: any, name: string) => voxaApp.onState(name, state)); + _.map(statesDefinition, (state: any, name: string) => + voxaApp.onState(name, state), + ); const onSessionEnded = simple.stub(); voxaApp.onSessionEnded(onSessionEnded); @@ -308,7 +338,9 @@ describe("VoxaApp", () => { it("should call onBeforeReplySent callbacks", async () => { const voxaApp = new VoxaApp({ Model, views, variables }); - _.map(statesDefinition, (state: any, name: string) => voxaApp.onState(name, state)); + _.map(statesDefinition, (state: any, name: string) => + voxaApp.onState(name, state), + ); const onBeforeReplySent = simple.stub(); voxaApp.onBeforeReplySent(onBeforeReplySent); @@ -324,8 +356,10 @@ describe("VoxaApp", () => { to: "die", }); - _.map(statesDefinition, (state: any, name: string) => voxaApp.onState(name, state)); - await voxaApp.execute(event, new AlexaReply()) ; + _.map(statesDefinition, (state: any, name: string) => + voxaApp.onState(name, state), + ); + await voxaApp.execute(event, new AlexaReply()); expect(statesDefinition.entry.called).to.be.true; }); @@ -342,14 +376,21 @@ describe("VoxaApp", () => { const voxaApp = new VoxaApp({ views, variables }); const alexaSkill = new AlexaPlatform(voxaApp); - voxaApp.onCanFulfillIntentRequest((alexaEvent: AlexaEvent, alexaReply: AlexaReply) => { - alexaReply.fulfillIntent("YES"); - alexaReply.fulfillSlot("slot1", "YES", "YES"); - return alexaReply; - }); + voxaApp.onCanFulfillIntentRequest( + (alexaEvent: AlexaEvent, alexaReply: AlexaReply) => { + alexaReply.fulfillIntent("YES"); + alexaReply.fulfillSlot("slot1", "YES", "YES"); + return alexaReply; + }, + ); - event = new AlexaEvent(rb.getCanFulfillIntentRequestRequest("NameIntent", { slot1: "something" })); - const reply = await alexaSkill.execute(event, new AlexaReply()) as AlexaReply; + event = new AlexaEvent( + rb.getCanFulfillIntentRequestRequest("NameIntent", { slot1: "something" }), + ); + const reply = (await alexaSkill.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.response.card).to.be.undefined; expect(reply.response.reprompt).to.be.undefined; @@ -372,8 +413,13 @@ describe("VoxaApp", () => { const voxaApp = new VoxaApp({ views, variables }); const alexaSkill = new AlexaPlatform(voxaApp, { defaultFulfillIntents }); - event = new AlexaEvent(rb.getCanFulfillIntentRequestRequest("NameIntent", { slot1: "something" })); - const reply = await alexaSkill.execute(event, new AlexaReply()) as AlexaReply; + event = new AlexaEvent( + rb.getCanFulfillIntentRequestRequest("NameIntent", { slot1: "something" }), + ); + const reply = (await alexaSkill.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.response.card).to.be.undefined; expect(reply.response.reprompt).to.be.undefined; @@ -394,14 +440,21 @@ describe("VoxaApp", () => { const voxaApp = new VoxaApp({ views, variables }); const alexaSkill = new AlexaPlatform(voxaApp); - voxaApp.onCanFulfillIntentRequest((alexaEvent: AlexaEvent, alexaReply: AlexaReply) => { - alexaReply.fulfillIntent("MAYBE"); - alexaReply.fulfillSlot("slot1", "YES", "YES"); - return alexaReply; - }); + voxaApp.onCanFulfillIntentRequest( + (alexaEvent: AlexaEvent, alexaReply: AlexaReply) => { + alexaReply.fulfillIntent("MAYBE"); + alexaReply.fulfillSlot("slot1", "YES", "YES"); + return alexaReply; + }, + ); - event = new AlexaEvent(rb.getCanFulfillIntentRequestRequest("NameIntent", { slot1: "something" })); - const reply = await alexaSkill.execute(event, new AlexaReply()) as AlexaReply; + event = new AlexaEvent( + rb.getCanFulfillIntentRequestRequest("NameIntent", { slot1: "something" }), + ); + const reply = (await alexaSkill.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.response.card).to.be.undefined; expect(reply.response.reprompt).to.be.undefined; @@ -416,13 +469,20 @@ describe("VoxaApp", () => { const voxaApp = new VoxaApp({ views, variables }); const alexaSkill = new AlexaPlatform(voxaApp); - voxaApp.onCanFulfillIntentRequest((alexaEvent: AlexaEvent, alexaReply: AlexaReply) => { - alexaReply.fulfillIntent("NO"); - return alexaReply; - }); + voxaApp.onCanFulfillIntentRequest( + (alexaEvent: AlexaEvent, alexaReply: AlexaReply) => { + alexaReply.fulfillIntent("NO"); + return alexaReply; + }, + ); - event = new AlexaEvent(rb.getCanFulfillIntentRequestRequest("NameIntent", { slot1: "something" })); - const reply = await alexaSkill.execute(event, new AlexaReply()) as AlexaReply; + event = new AlexaEvent( + rb.getCanFulfillIntentRequestRequest("NameIntent", { slot1: "something" }), + ); + const reply = (await alexaSkill.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.response.card).to.be.undefined; expect(reply.response.reprompt).to.be.undefined; @@ -443,14 +503,21 @@ describe("VoxaApp", () => { const voxaApp = new VoxaApp({ views, variables }); const alexaSkill = new AlexaPlatform(voxaApp); - voxaApp.onCanFulfillIntentRequest((alexaEvent: AlexaEvent, alexaReply: AlexaReply) => { - alexaReply.fulfillIntent("yes"); - alexaReply.fulfillSlot("slot1", "yes", "yes"); - return alexaReply; - }); + voxaApp.onCanFulfillIntentRequest( + (alexaEvent: AlexaEvent, alexaReply: AlexaReply) => { + alexaReply.fulfillIntent("yes"); + alexaReply.fulfillSlot("slot1", "yes", "yes"); + return alexaReply; + }, + ); - event = new AlexaEvent(rb.getCanFulfillIntentRequestRequest("NameIntent", { slot1: "something" })); - const reply = await alexaSkill.execute(event, new AlexaReply()) as AlexaReply; + event = new AlexaEvent( + rb.getCanFulfillIntentRequestRequest("NameIntent", { slot1: "something" }), + ); + const reply = (await alexaSkill.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.response.card).to.be.undefined; expect(reply.response.reprompt).to.be.undefined; @@ -459,23 +526,33 @@ describe("VoxaApp", () => { }); describe("onUnhandledState", () => { - it("should call onUnhandledState callbacks when the state" + - " machine transition throws a UnhandledState error", async () => { - const voxaApp = new VoxaApp({ Model, views, variables }); - const onUnhandledState = simple.stub().resolveWith({ - tell: "ExitIntent.Farewell", - }); - - voxaApp.onUnhandledState(onUnhandledState); - - event.intent.name = "LaunchIntent"; - statesDefinition.entry = simple.stub().resolveWith(null); + it( + "should call onUnhandledState callbacks when the state" + + " machine transition throws a UnhandledState error", + async () => { + const voxaApp = new VoxaApp({ Model, views, variables }); + const onUnhandledState = simple.stub().resolveWith({ + tell: "ExitIntent.Farewell", + }); - _.map(statesDefinition, (state: any, name: string) => voxaApp.onState(name, state)); - const reply = await voxaApp.execute(event, new AlexaReply()) as AlexaReply; - expect(onUnhandledState.called).to.be.true; - expect(reply.speech).to.equal("Ok. For more info visit example.com site."); - }); + voxaApp.onUnhandledState(onUnhandledState); + + event.intent.name = "LaunchIntent"; + statesDefinition.entry = simple.stub().resolveWith(null); + + _.map(statesDefinition, (state: any, name: string) => + voxaApp.onState(name, state), + ); + const reply = (await voxaApp.execute( + event, + new AlexaReply(), + )) as AlexaReply; + expect(onUnhandledState.called).to.be.true; + expect(reply.speech).to.equal( + "Ok. For more info visit example.com site.", + ); + }, + ); }); it("should include all directives in the reply", async () => { @@ -489,21 +566,26 @@ describe("VoxaApp", () => { to: "entry", })); - const reply = await voxaApp.execute(event, new AlexaReply()) as AlexaReply; + const reply = (await voxaApp.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.response.directives).to.not.be.undefined; expect(reply.response.directives).to.have.length(1); - expect(reply.response.directives).to.deep.equal([{ - audioItem: { - metadata: {}, - stream: { - offsetInMilliseconds: 0, - token: "123", - url: "url", + expect(reply.response.directives).to.deep.equal([ + { + audioItem: { + metadata: {}, + stream: { + offsetInMilliseconds: 0, + token: "123", + url: "url", + }, }, + playBehavior: "REPLACE_ALL", + type: "AudioPlayer.Play", }, - playBehavior: "REPLACE_ALL", - type: "AudioPlayer.Play", - }]); + ]); }); it("should include all directives in the reply even if die", async () => { @@ -513,24 +595,29 @@ describe("VoxaApp", () => { voxaApp.onIntent("SomeIntent", () => ({ directives, - reply: "ExitIntent.Farewell", + say: "ExitIntent.Farewell", })); - const reply = await voxaApp.execute(event, new AlexaReply()) as AlexaReply; + const reply = (await voxaApp.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.response.directives).to.not.be.undefined; expect(reply.response.directives).to.have.length(1); - expect(reply.response.directives).to.deep.equal([{ - audioItem: { - metadata: {}, - stream: { - offsetInMilliseconds: 0, - token: "123", - url: "url", + expect(reply.response.directives).to.deep.equal([ + { + audioItem: { + metadata: {}, + stream: { + offsetInMilliseconds: 0, + token: "123", + url: "url", + }, }, + playBehavior: "REPLACE_ALL", + type: "AudioPlayer.Play", }, - playBehavior: "REPLACE_ALL", - type: "AudioPlayer.Play", - }]); + ]); }); it("should render all messages after each transition", async () => { @@ -551,22 +638,34 @@ describe("VoxaApp", () => { return { tell: "Count.Tell", to: "die" }; }; - _.map(statesDefinition, (state: any, name: string) => voxaApp.onState(name, state)); - const reply = await voxaApp.execute(event, new AlexaReply()) as AlexaReply; + _.map(statesDefinition, (state: any, name: string) => + voxaApp.onState(name, state), + ); + const reply = (await voxaApp.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(reply.speech).to.deep.equal("0\n1"); }); it("should call onIntentRequest callbacks before the statemachine", async () => { const voxaApp = new VoxaApp({ views, variables }); - _.map(statesDefinition, (state: any, name: string) => voxaApp.onState(name, state)); + _.map(statesDefinition, (state: any, name: string) => + voxaApp.onState(name, state), + ); const stubResponse = "STUB RESPONSE"; const stub = simple.stub().resolveWith(stubResponse); voxaApp.onIntentRequest(stub); - const reply = await voxaApp.execute(event, new AlexaReply()) as AlexaReply; + const reply = (await voxaApp.execute( + event, + new AlexaReply(), + )) as AlexaReply; expect(stub.called).to.be.true; expect(reply).to.not.equal(stubResponse); - expect(reply.speech).to.equal("Ok. For more info visit example.com site."); + expect(reply.speech).to.equal( + "Ok. For more info visit example.com site.", + ); }); describe("onRequestStarted", () => { @@ -582,4 +681,60 @@ describe("VoxaApp", () => { expect(spy.called).to.be.true; }); }); + + describe("Reply", () => { + it("should pick up the say and reprompt statements", async () => { + const voxaApp = new VoxaApp({ views, variables }); + event.intent.name = "LaunchIntent"; + voxaApp.onIntent("LaunchIntent", { + flow: "yield", + reply: "Reply.Say", + to: "entry", + }); + const response = (await voxaApp.execute( + event, + new AlexaReply(), + )) as AlexaReply; + expect(response.speech).to.deep.equal("this is a say"); + expect(response.reprompt).to.deep.equal( + "this is a reprompt", + ); + expect(response.hasTerminated).to.be.false; + }); + + it("should pick up Hint and card statements", async () => { + const voxaApp = new VoxaApp({ views, variables }); + voxaApp.onIntent("SomeIntent", { + flow: "yield", + reply: "Reply.Card", + reprompt: "Reprompt", + say: "Say", + to: "entry", + }); + const alexaSkill = new AlexaPlatform(voxaApp); + const response = await alexaSkill.execute(event, {}); + + expect(_.get(response, "response.outputSpeech.ssml")).to.deep.equal( + "say", + ); + expect( + _.get(response, "response.reprompt.outputSpeech.ssml"), + ).to.deep.equal("reprompt"); + expect(response.response.card).to.deep.equal({ + image: { + largeImageUrl: "https://example.com/large.jpg", + smallImageUrl: "https://example.com/small.jpg", + }, + title: "Title", + type: "Standard", + }); + expect(_.get(response, "response.directives[0]")).to.deep.equal({ + hint: { + text: "this is the hint", + type: "PlainText", + }, + type: "Hint", + }); + }); + }); }); diff --git a/test/VoxaEvent.spec.ts b/test/VoxaEvent.spec.ts deleted file mode 100644 index 09a918d0..00000000 --- a/test/VoxaEvent.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import("mocha"); - -import { expect } from "chai"; - -import { IVoxaEvent } from "../src/VoxaEvent"; - -class Event extends IVoxaEvent { - public get supportedInterfaces() { - return []; - } - - public requestToIntent = { - LaunchRequest: "LaunchIntent", - }; - - public requestToRequest = { - CiaoRequest: "SessionEndedRequest", - }; - - public constructor(raw: any, context: any) { - super(raw, context); - this.request = raw.request; - } -} - -describe("VoxaEvent", () => { - it("should map some requests to intents", () => { - const raw = { - request: { - type: "LaunchRequest", - }, - }; - const event = new Event(raw, {}); - event.mapRequestToIntent(); - expect(event.request.type).to.equal("IntentRequest"); - if (!event.intent) { - throw new Error("event.intent is undefined"); - } - expect(event.intent.name).to.equal("LaunchIntent"); - }); - - it("should not map requests to intents if not specified", () => { - const raw = { - request: { - type: "ExitRequest", - }, - }; - const event = new Event(raw, {}); - event.mapRequestToIntent(); - expect(event.request.type).to.equal("ExitRequest"); - expect(event.intent).to.be.undefined; - }); - - it("should map requests to other requests if specified", () => { - const raw = { - request: { - type: "CiaoRequest", - }, - }; - const event = new Event(raw, {}); - event.mapRequestToRequest(); - expect(event.request.type).to.equal("SessionEndedRequest"); - }); - - it("should map requests to other requests if specified", () => { - const raw = { - request: { - type: "ExitRequest", - }, - }; - const event = new Event(raw, {}); - event.mapRequestToRequest(); - expect(event.request.type).to.equal("ExitRequest"); - }); -}); diff --git a/test/directives.spec.ts b/test/directives.spec.ts index 2017521e..14d2e7c7 100644 --- a/test/directives.spec.ts +++ b/test/directives.spec.ts @@ -1,11 +1,12 @@ import { expect, use } from "chai"; import * as i18n from "i18next"; import "mocha"; -import { Ask, Reply, Reprompt, Say, Tell } from "../src/directives"; +import { Ask, Reprompt, Say, Tell } from "../src/directives"; import { AlexaEvent } from "../src/platforms/alexa/AlexaEvent"; import { AlexaReply } from "../src/platforms/alexa/AlexaReply"; import { Hint } from "../src/platforms/alexa/directives"; import { Renderer } from "../src/renderers/Renderer"; +import { VoxaApp } from "../src/VoxaApp"; import { IVoxaEvent } from "../src/VoxaEvent"; import { AlexaRequestBuilder } from "./tools"; import { variables } from "./variables"; @@ -14,9 +15,10 @@ import { views } from "./views"; describe("directives", () => { let response: AlexaReply; let event: IVoxaEvent; + let voxaApp: VoxaApp; before(() => { - i18n .init({ + i18n.init({ load: "all", nonExplicitWhitelist: true, resources: views, @@ -24,6 +26,9 @@ describe("directives", () => { }); beforeEach(() => { + voxaApp = new VoxaApp({ + views: {}, + }); const rb = new AlexaRequestBuilder(); const renderer = new Renderer({ views, variables }); event = new AlexaEvent(rb.getIntentRequest("AMAZON.YesIntent")); @@ -33,76 +38,82 @@ describe("directives", () => { response = new AlexaReply(); }); - describe("reply", () => { - it("should pick up the directive statements", async () => { - const transition: any = {}; - await new Reply("Reply.Directives").writeToReply(response, event, transition); - expect(transition.directives).to.have.lengthOf(1); - expect(transition.directives[0]).to.be.an.instanceof(Hint); - expect(response.hasTerminated).to.be.false; - }); - - it("should pick up the tell statements", async () => { - await new Reply("Reply.Tell").writeToReply(response, event, {}); - expect(response.speech).to.deep.equal("this is a tell"); - expect(response.hasTerminated).to.be.true; - }); - - it("should pick up the ask statements", async () => { - await new Reply("Reply.Ask").writeToReply(response, event, {}); - expect(response.speech).to.deep.equal("this is an ask"); - expect(response.reprompt).to.deep.equal("this is a reprompt"); - expect(response.hasTerminated).to.be.false; - }); - - it("should ask and directives and reprmpt", async () => { - const transition: any = {}; - await new Reply("Reply.Combined").writeToReply(response, event, transition); - expect(response.speech).to.deep.equal("this is an ask"); - expect(response.reprompt).to.deep.equal("this is a reprompt"); - expect(transition.directives).to.have.lengthOf(1); - expect(transition.directives[0]).to.be.an.instanceof(Hint); - expect(response.hasTerminated).to.be.false; - }); - - }); - describe("tell", () => { it("should end the session", async () => { - await new Tell("Question.Ask.ask").writeToReply(response, event, {}); - expect(response.speech).to.deep.equal("What time is it?"); + await new Tell("Tell").writeToReply(response, event, {}); + expect(response.speech).to.equal("tell"); + expect(response.hasTerminated).to.be.true; + }); + it("should render a random tell from the list", async () => { + await new Tell("TellRandom").writeToReply(response, event, {}); + expect([ + "tell1", + "tell2", + "tell3", + ]).to.include(response.speech); expect(response.hasTerminated).to.be.true; }); }); describe("ask", () => { it("should render ask statements", async () => { - await new Ask("Question.Ask.ask").writeToReply(response, event, {}); + await new Ask("Ask").writeToReply(response, event, {}); expect(response.speech).to.deep.equal("What time is it?"); }); + it("should render Random Ask statements", async () => { + await new Ask("AskRandomObj").writeToReply(response, event, {}); + expect([ + "ask1", + "ask2", + "ask3", + ]).to.include(response.speech); + + expect([ + "reprompt1", + "reprompt2", + "reprompt3", + ]).to.include(response.reprompt); + }); it("should not terminate the session", async () => { - await new Ask("Question.Ask.ask").writeToReply(response, event, {}); + await new Ask("Ask").writeToReply(response, event, {}); expect(response.hasTerminated).to.be.false; }); }); describe("say", () => { it("should render ask statements", async () => { - await new Say("Question.Ask.ask").writeToReply(response, event, {}); - expect(response.speech).to.deep.equal("What time is it?"); + await new Say("Say").writeToReply(response, event, {}); + expect(response.speech).to.deep.equal("say"); }); it("should not terminate the session", async () => { - await new Say("Question.Ask.ask").writeToReply(response, event, {}); + await new Say("Say").writeToReply(response, event, {}); expect(response.hasTerminated).to.be.false; }); + + it("should render a random Say ", async () => { + await new Say("SayRandom").writeToReply(response, event, {}); + expect([ + "say1", + "say2", + "say3", + ]).to.include(response.speech); + }); }); describe("reprompt", () => { it("should render reprompt statements", async () => { - await new Reprompt("Question.Ask.ask").writeToReply(response, event, {}); - expect(response.reprompt).to.equal("What time is it?"); + await new Reprompt("Reprompt").writeToReply(response, event, {}); + expect(response.reprompt).to.equal("reprompt"); + }); + it("should render random reprompt statements", async () => { + await new Reprompt("RepromptRandom").writeToReply(response, event, {}); + expect([ + "reprompt1", + "reprompt2", + "reprompt3", + ]).to.include(response.reprompt); }); }); }); diff --git a/test/tools.ts b/test/tools.ts index 4339c535..d4a00971 100644 --- a/test/tools.ts +++ b/test/tools.ts @@ -28,7 +28,9 @@ export class AlexaRequestBuilder { this.deviceId = applicationId || `amzn1.ask.device.${v1()}`; } - public getSessionEndedRequest(reason: SessionEndedReason = "ERROR"): RequestEnvelope { + public getSessionEndedRequest( + reason: SessionEndedReason = "ERROR", + ): RequestEnvelope { return { context: this.getContextData(), request: { @@ -43,7 +45,25 @@ export class AlexaRequestBuilder { }; } - public getCanFulfillIntentRequestRequest(intentName: string, slots?: any): RequestEnvelope { + public getDisplayElementSelectedRequest(token: string): RequestEnvelope { + return { + context: this.getContextData(), + request: { + locale: "en-US", + requestId: `EdwRequestId.${v1()}`, + timestamp: new Date().toISOString(), + token, + type: "Display.ElementSelected", + }, + session: this.getSessionData(), + version: this.version, + }; + } + + public getCanFulfillIntentRequestRequest( + intentName: string, + slots?: any, + ): RequestEnvelope { if (!slots) { slots = {}; } else { @@ -57,7 +77,6 @@ export class AlexaRequestBuilder { return { context: this.getContextData(), request: { - dialogState: "STARTED", intent: { name: intentName, slots, confirmationStatus: "NONE" }, locale: "en-US", requestId: `EdwRequestId.${v1()}`, @@ -151,7 +170,7 @@ export class AlexaRequestBuilder { } public getPlaybackStoppedRequest(token?: string): RequestEnvelope { - const request: interfaces.audioplayer.PlaybackStoppedRequest = { + const request: interfaces.audioplayer.PlaybackStoppedRequest = { locale: "en-US", requestId: "EdwRequestId." + v1(), timestamp: new Date().toISOString(), @@ -167,8 +186,10 @@ export class AlexaRequestBuilder { }; } - public getGameEngineInputHandlerEventRequest(buttonsRecognized: number = 1): RequestEnvelope { - const request: interfaces.gameEngine.InputHandlerEventRequest = { + public getGameEngineInputHandlerEventRequest( + buttonsRecognized: number = 1, + ): RequestEnvelope { + const request: interfaces.gameEngine.InputHandlerEventRequest = { events: [], locale: "en-US", requestId: `amzn1.echo-api.request.${v1()}`, @@ -212,7 +233,8 @@ export class AlexaRequestBuilder { name: string, token: string, payload: any, - status?: interfaces.connections.ConnectionsStatus): RequestEnvelope { + status?: interfaces.connections.ConnectionsStatus, + ): RequestEnvelope { status = status || { code: "200", message: "OK" }; const request: interfaces.connections.ConnectionsResponse = { @@ -235,7 +257,9 @@ export class AlexaRequestBuilder { } } -export function getLambdaContext(callback: AWSLambdaCallback): AWSLambdaContext { +export function getLambdaContext( + callback: AWSLambdaCallback, +): AWSLambdaContext { return { awsRequestId: "aws://", callbackWaitsForEmptyEventLoop: false, @@ -249,7 +273,7 @@ export function getLambdaContext(callback: AWSLambdaCallback): AWSLambdaCon getRemainingTimeInMillis: () => 1000, done: callback, - fail: (err: Error|string) => { + fail: (err: Error | string) => { if (_.isString(err)) { return callback(new Error(err)); } @@ -260,7 +284,10 @@ export function getLambdaContext(callback: AWSLambdaCallback): AWSLambdaCon }; } -export function getAPIGatewayProxyEvent(method: string = "GET", body: string|null = null): APIGatewayProxyEvent { +export function getAPIGatewayProxyEvent( + method: string = "GET", + body: string | null = null, +): APIGatewayProxyEvent { return { body, headers: {}, diff --git a/test/views.ts b/test/views.ts index dfd2d9ff..c9b1c27e 100644 --- a/test/views.ts +++ b/test/views.ts @@ -1,17 +1,15 @@ -export const views = { +export const views = { "de-DE": { translation: { + Ask: "wie spät ist es?", ExitIntent: { - Farewell: "Ok für weitere Infos besuchen {site} Website" , + Farewell: "Ok für weitere Infos besuchen {site} Website", }, LaunchIntent: { OpenResponse: "Hallo! guten {time}", }, Number: { - One: "{numberOne}" , - }, - Question: { - Ask: "wie spät ist es?" , + One: "{numberOne}", }, RandomResponse: [ "zufällig1", @@ -20,13 +18,20 @@ export const views = { "zufällig4", "zufällig5", ], - Say: { - Say: "sagen" , - }, + Say: "sagen", }, }, "en-US": { translation: { + Ask: { + ask: "What time is it?", + reprompt: "What time is it?", + }, + AskRandom: ["ask1", "ask2", "ask3"], + AskRandomObj: { + ask: ["ask1", "ask2", "ask3"], + reprompt: ["reprompt1", "reprompt2", "reprompt3"], + }, BadInput: { RepeatLastAskReprompt: { say: "I'm sorry. I didn't understand.", @@ -47,8 +52,8 @@ export const views = { }, Card2: "{card2}", Count: { - Say: "{count}", - Tell: "{count}" , + Say: "{count}", + Tell: "{count}", }, CustomerContact: { FullInfo: "Welcome {customerContactGivenName}, your email address is {customerContactEmail}, and your phone number is {customerContactCountry} {customerContactNumber}", @@ -69,7 +74,7 @@ export const views = { title: "Example.com", }, display: "DEFAULT", - image: { + image: { url: "https://example.com/image.png", }, subtitle: "subtitle", @@ -102,7 +107,7 @@ export const views = { }, Help: "This is the help", HelpIntent: { - HelpAboutSkill: "For more help visit www.rain.agency" , + HelpAboutSkill: "For more help visit www.rain.agency", }, Hint: "string", ISP: { @@ -129,26 +134,14 @@ export const views = { WithItems: "Lists with items are: {listsWithItems}", }, Number: { - One: "{numberOne}" , + One: "{numberOne}", }, Playing: { SayStop: { ask: "Say stop if you want to finish the playback", }, }, - Question: { - Ask: { - ask: "What time is it?", - reprompt: "What time is it?", - }, - - }, - RandomResponse: [ - "Random 1", - "Random 2", - "Random 3", - "Random 4", - ], + RandomResponse: ["Random 1", "Random 2", "Random 3", "Random 4"], RenderTemplate: { template: { backButton: "VISIBLE", @@ -174,25 +167,28 @@ export const views = { type: "Display.RenderTemplate", }, Reply: { - Ask: { - ask: "this is an ask", - reprompt: "this is a reprompt", + Card: { + alexaCard: { + image: { + largeImageUrl: "https://example.com/large.jpg", + smallImageUrl: "https://example.com/small.jpg", + }, + title: "Title", + type: "Standard", + }, + alexaHint: "this is the hint", }, - Combined: { - ask: "this is an ask", - directives: ["{hintDirective}"], + Say: { reprompt: "this is a reprompt", + say: "this is a say", }, - Directives: { - directives: ["{hintDirective}"], - }, - Tell: { - tell: "this is a tell", - }, - }, - Say: { - Say: "say" , }, + Reprompt: "reprompt", + RepromptRandom: ["reprompt1", "reprompt2", "reprompt3"], + Say: "say", + SayRandom: ["say1", "say2", "say3"], + Tell: "tell", + TellRandom: ["tell1", "tell2", "tell3"], }, }, }; diff --git a/yarn.lock b/yarn.lock index f22eed04..4e3a536b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,12 +11,12 @@ resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-0.0.33.tgz#17083d9c569048792f4f33ef6cd6aaf54d5b22d3" "@types/aws-lambda@^8.10.6": - version "8.10.7" - resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.7.tgz#1dfd0e1918205f74169658ad8aa8f9c06f161909" + version "8.10.11" + resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.11.tgz#2c51a7dacb1abf880d35778aca68ed1075d0b300" "@types/bluebird@*", "@types/bluebird@^3.5.18": - version "3.5.20" - resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.20.tgz#f6363172add6f4eabb8cada53ca9af2781e8d6a1" + version "3.5.24" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.24.tgz#11f76812531c14f793b8ecbf1de96f672905de8a" "@types/body-parser@*": version "1.17.0" @@ -98,8 +98,8 @@ resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.2.tgz#6ee7cd395effe5ec80b515d3ff1699068cd0cd1d" "@types/i18next@^8.4.2": - version "8.4.3" - resolved "https://registry.yarnpkg.com/@types/i18next/-/i18next-8.4.3.tgz#9136a9551bf5bf7169aa9f3125c1743f1f8dd6de" + version "8.4.5" + resolved "https://registry.yarnpkg.com/@types/i18next/-/i18next-8.4.5.tgz#d6c0cb594258815219c70006e9f5cd65ad6d797f" "@types/jsonwebtoken@^7.2.6": version "7.2.7" @@ -112,8 +112,8 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.104.tgz#53ee2357fa2e6e68379341d92eb2ecea4b11bb80" "@types/lodash@^4.14.88": - version "4.14.110" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.110.tgz#fb07498f84152947f30ea09d89207ca07123461e" + version "4.14.116" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.116.tgz#5ccf215653e3e8c786a58390751033a9adca0eb9" "@types/marked@0.3.0": version "0.3.0" @@ -138,16 +138,16 @@ "@types/node" "*" "@types/node@*": - version "10.3.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.3.5.tgz#8423cdf6e6fb83433e489900d7600d3b61c8260c" + version "10.9.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.4.tgz#0f4cb2dc7c1de6096055357f70179043c33e9897" "@types/node@^8.0.58": - version "8.10.20" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.20.tgz#fe674ea52e13950ab10954433a7824438aabbcac" + version "8.10.29" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.29.tgz#b3a13b58dd7b0682bf1b42022bef4a5a9718f687" "@types/node@^9.4.6": - version "9.6.22" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.22.tgz#05b55093faaadedea7a4b3f76e9a61346a6dd209" + version "9.6.31" + resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.31.tgz#4d1722987f8d808b4c194dceb8c213bd92f028e5" "@types/node@^9.6.1": version "9.6.20" @@ -158,13 +158,22 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.2.tgz#fa8e1ad1d474688a757140c91de6dace6f4abc8d" "@types/request-promise@^4.1.39": - version "4.1.41" - resolved "https://registry.yarnpkg.com/@types/request-promise/-/request-promise-4.1.41.tgz#1e254d51362d7edcb714b60cde303bcbe0ab1ee0" + version "4.1.42" + resolved "https://registry.yarnpkg.com/@types/request-promise/-/request-promise-4.1.42.tgz#a70a6777429531e60ed09faa077ead9b995204cd" dependencies: "@types/bluebird" "*" "@types/request" "*" -"@types/request@*", "@types/request@^2.47.0": +"@types/request@*": + version "2.47.1" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.47.1.tgz#25410d3afbdac04c91a94ad9efc9824100735824" + dependencies: + "@types/caseless" "*" + "@types/form-data" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + +"@types/request@^2.47.0": version "2.47.0" resolved "https://registry.yarnpkg.com/@types/request/-/request-2.47.0.tgz#76a666cee4cb85dcffea6cd4645227926d9e114e" dependencies: @@ -211,22 +220,22 @@ "@types/node" "*" "@types/tough-cookie@*": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.2.tgz#e0d481d8bb282ad8a8c9e100ceb72c995fb5e709" + version "2.3.3" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.3.tgz#7f226d67d654ec9070e755f46daebf014628e9d9" "@types/url-join@^0.8.1", "@types/url-join@^0.8.2": version "0.8.2" resolved "https://registry.yarnpkg.com/@types/url-join/-/url-join-0.8.2.tgz#1181ecbe1d97b7034e0ea1e35e62e86cc26b422d" "@types/uuid@^3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.3.tgz#121ace265f5569ce40f4f6d0ff78a338c732a754" + version "3.4.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.4.tgz#7af69360fa65ef0decb41fd150bf4ca5c0cefdf5" dependencies: "@types/node" "*" actions-on-google@2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/actions-on-google/-/actions-on-google-2.2.0.tgz#73a1b0ba9c2d8f3f4f25400a2c8e1fbde71b92d5" + version "2.3.0" + resolved "https://registry.yarnpkg.com/actions-on-google/-/actions-on-google-2.3.0.tgz#4da4f104f1af99b08e065811bdb5e9f8ed6db31d" dependencies: "@types/aws-lambda" "^0.0.33" "@types/debug" "^0.0.30" @@ -236,7 +245,7 @@ actions-on-google@2: google-auth-library "^1.3.2" googleapis "^27.0.0" -ajv@^5.1.0: +ajv@^5.1.0, ajv@^5.3.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" dependencies: @@ -315,35 +324,15 @@ asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" -ask-sdk-core@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/ask-sdk-core/-/ask-sdk-core-2.0.7.tgz#0f2300aa99a5bd92c0bd700d1ed92ff8da1f83b9" - -ask-sdk-dynamodb-persistence-adapter@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/ask-sdk-dynamodb-persistence-adapter/-/ask-sdk-dynamodb-persistence-adapter-2.0.7.tgz#c00c88579cd32cf91b1b3d843615ff29cadc5234" - dependencies: - aws-sdk "^2.163.0" - -ask-sdk-model@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/ask-sdk-model/-/ask-sdk-model-1.3.1.tgz#a583809e4e9d3fb5308306249907808fab37fd4f" - ask-sdk-model@^1.4.0-beta.1: version "1.4.0-beta.1" resolved "https://registry.yarnpkg.com/ask-sdk-model/-/ask-sdk-model-1.4.0-beta.1.tgz#d45a3758f95f4de7f2643a2db6002fb01ca58a71" -ask-sdk@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/ask-sdk/-/ask-sdk-2.0.7.tgz#1157fb90399b50bb1deab55a866255fba4317d72" - dependencies: - ask-sdk-core "^2.0.7" - ask-sdk-dynamodb-persistence-adapter "^2.0.7" - ask-sdk-model "^1.0.0" - asn1@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + dependencies: + safer-buffer "~2.1.0" assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" @@ -375,7 +364,7 @@ atob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.1.tgz#ae2d5a729477f289d60dd7f96a6314a22dd6c22a" -aws-sdk@^2.163.0, aws-sdk@^2.246.1: +aws-sdk@^2.246.1: version "2.263.1" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.263.1.tgz#217e2459ebff3702cc72260bcafca0bf16edc4cd" dependencies: @@ -393,13 +382,13 @@ aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" -aws4@^1.6.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289" +aws4@^1.6.0, aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" axios@^0.18.0: version "0.18.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102" + resolved "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102" dependencies: follow-redirects "^1.3.0" is-buffer "^1.1.5" @@ -520,8 +509,8 @@ base@^0.11.1: pascalcase "^0.1.1" bcrypt-pbkdf@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" dependencies: tweetnacl "^0.14.3" @@ -544,10 +533,14 @@ bl@^1.2.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" -bluebird@^3.5.0, bluebird@^3.5.1: +bluebird@^3.5.0: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" +bluebird@^3.5.1: + version "3.5.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.2.tgz#1be0908e054a751754549c270489c1505d4ab15a" + botbuilder-azure@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/botbuilder-azure/-/botbuilder-azure-3.1.0.tgz#bc777986443626065f6cae203d7ec79c176ca601" @@ -617,8 +610,8 @@ buffer-equal-constant-time@1.0.1: resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" buffer-from@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" buffer@4.9.1: version "4.9.1" @@ -692,7 +685,7 @@ chai@^4.1.2: chalk@^1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + resolved "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: ansi-styles "^2.2.1" escape-string-regexp "^1.0.2" @@ -759,16 +752,16 @@ collection-visit@^1.0.0: object-visit "^1.0.0" color-convert@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" dependencies: - color-name "^1.1.1" + color-name "1.1.3" -color-name@^1.1.1: +color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" -combined-stream@1.0.6, combined-stream@~1.0.5: +combined-stream@1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" dependencies: @@ -785,8 +778,8 @@ commander@2.9.0: graceful-readlink ">= 1.0.0" commander@^2.12.1: - version "2.15.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" commondir@^1.0.1: version "1.0.1" @@ -817,14 +810,15 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" coveralls@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-3.0.1.tgz#12e15914eaa29204e56869a5ece7b9e1492d2ae2" + version "3.0.2" + resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-3.0.2.tgz#f5a0bcd90ca4e64e088b710fa8dda640aea4884f" dependencies: - js-yaml "^3.6.1" + growl "~> 1.10.0" + js-yaml "^3.11.0" lcov-parse "^0.0.10" - log-driver "^1.2.5" + log-driver "^1.2.7" minimist "^1.2.0" - request "^2.79.0" + request "^2.85.0" cross-spawn@^4: version "4.0.2" @@ -955,10 +949,11 @@ documentdb@^1.13.0: underscore "1.8.3" ecc-jsbn@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" dependencies: jsbn "~0.1.0" + safer-buffer "^2.1.0" ecdsa-sig-formatter@1.0.10: version "1.0.10" @@ -977,8 +972,8 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" esprima@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" esutils@^2.0.2: version "2.0.2" @@ -1025,9 +1020,9 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.1, extend@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" +extend@^3.0.1, extend@~3.0.1, extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" extend@~1.2.1: version "1.2.1" @@ -1093,8 +1088,8 @@ find-up@^2.1.0: locate-path "^2.0.0" follow-redirects@^1.3.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.0.tgz#234f49cf770b7f35b40e790f636ceba0c3a0ab77" + version "1.5.7" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.7.tgz#a39e4804dacb90202bca76a9e2ac10433ca6a69a" dependencies: debug "^3.1.0" @@ -1113,7 +1108,7 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" -form-data@~2.3.1: +form-data@~2.3.1, form-data@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" dependencies: @@ -1180,7 +1175,7 @@ glob@7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.1.2, glob@^7.0.0, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1: +glob@7.1.2, glob@^7.0.0, glob@^7.0.5, glob@^7.0.6: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -1191,6 +1186,17 @@ glob@7.1.2, glob@^7.0.0, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.1: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -1240,6 +1246,10 @@ growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" +"growl@~> 1.10.0": + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + gtoken@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-2.3.0.tgz#4e0ffc16432d7041a1b3dbc1d97aac17a5dc964a" @@ -1271,6 +1281,13 @@ har-validator@~5.0.3: ajv "^5.1.0" har-schema "^2.0.0" +har-validator@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29" + dependencies: + ajv "^5.3.0" + har-schema "^2.0.0" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -1612,20 +1629,13 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@^3.6.1: +js-yaml@^3.11.0, js-yaml@^3.7.0: version "3.12.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" dependencies: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^3.7.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -1810,7 +1820,7 @@ lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" -log-driver@^1.2.5: +log-driver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" @@ -1896,15 +1906,15 @@ micromatch@^3.1.10, micromatch@^3.1.8: snapdragon "^0.8.1" to-regex "^3.0.2" -mime-db@~1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" +mime-db@~1.36.0: + version "1.36.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397" -mime-types@^2.1.12, mime-types@~2.1.17: - version "2.1.18" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" +mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19: + version "2.1.20" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19" dependencies: - mime-db "~1.33.0" + mime-db "~1.36.0" mime@^2.2.0: version "2.3.1" @@ -1922,11 +1932,11 @@ minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: minimist@0.0.8: version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" minimist@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + resolved "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" minimist@~0.0.1: version "0.0.10" @@ -2044,8 +2054,8 @@ nock@^9.6.1: semver "^5.5.0" node-forge@^0.7.4: - version "0.7.5" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df" + version "0.7.6" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" normalize-package-data@^2.3.2: version "2.4.0" @@ -2102,6 +2112,10 @@ oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -2204,8 +2218,8 @@ path-key@^2.0.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" path-parse@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" path-type@^1.0.0: version "1.1.0" @@ -2248,8 +2262,8 @@ pkg-dir@^1.0.0: find-up "^1.0.0" portfinder@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" + version "1.0.17" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.17.tgz#a8a1691143e46c4735edefcf4fbcccedad26456a" dependencies: async "^1.5.2" debug "^2.2.0" @@ -2289,6 +2303,10 @@ pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" +psl@^1.1.24: + version "1.1.29" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" + punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" @@ -2301,7 +2319,7 @@ punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" -qs@^6.5.1, qs@~6.5.1: +qs@^6.5.1, qs@~6.5.1, qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -2393,7 +2411,7 @@ request-promise@^4.2.2: stealthy-require "^1.1.0" tough-cookie ">=2.3.3" -request@^2.69.0, request@^2.79.0, request@^2.83.0, request@^2.86.0: +request@^2.69.0, request@^2.86.0: version "2.87.0" resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" dependencies: @@ -2418,6 +2436,31 @@ request@^2.69.0, request@^2.79.0, request@^2.83.0, request@^2.86.0: tunnel-agent "^0.6.0" uuid "^3.1.0" +request@^2.83.0, request@^2.85.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -2434,12 +2477,18 @@ resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" -resolve@^1.1.6, resolve@^1.3.2: +resolve@^1.1.6: version "1.7.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" dependencies: path-parse "^1.0.5" +resolve@^1.3.2: + version "1.8.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" + dependencies: + path-parse "^1.0.5" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -2464,21 +2513,17 @@ rsa-pem-from-mod-exp@^0.8.4: version "0.8.4" resolved "https://registry.yarnpkg.com/rsa-pem-from-mod-exp/-/rsa-pem-from-mod-exp-0.8.4.tgz#362a42c6d304056d493b3f12bceabb2c6576a6d4" -safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" -safe-buffer@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -2498,10 +2543,14 @@ semaphore@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.0.5.tgz#b492576e66af193db95d65e25ec53f5f19798d60" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0: +"semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" +semver@^5.3.0: + version "5.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -2598,8 +2647,8 @@ source-map-support@^0.5.0: source-map "^0.6.0" source-map-support@^0.5.3: - version "0.5.6" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13" + version "0.5.9" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -2670,13 +2719,14 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" sshpk@^1.7.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.1.tgz#130f5975eddad963f1d56f92b9ac6c51fa9f83eb" + version "1.14.2" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" dashdash "^1.12.0" getpass "^0.1.1" + safer-buffer "^2.0.2" optionalDependencies: bcrypt-pbkdf "^1.0.0" ecc-jsbn "~0.1.1" @@ -2780,8 +2830,8 @@ supports-color@^3.1.2: has-flag "^1.0.0" supports-color@^5.3.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" dependencies: has-flag "^3.0.0" @@ -2846,6 +2896,13 @@ tough-cookie@>=2.3.3, tough-cookie@~2.3.3: dependencies: punycode "^1.4.1" +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" @@ -2875,8 +2932,8 @@ tsconfig@^7.0.0: strip-json-comments "^2.0.0" tslib@^1.8.0, tslib@^1.8.1: - version "1.9.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" + version "1.9.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" tslint-no-unused-expression-chai@^0.0.3: version "0.0.3" @@ -2885,8 +2942,8 @@ tslint-no-unused-expression-chai@^0.0.3: tsutils "^2.3.0" tslint@^5.8.0: - version "5.10.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.10.0.tgz#11e26bccb88afa02dd0d9956cae3d4540b5f54c3" + version "5.11.0" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.11.0.tgz#98f30c02eae3cde7006201e4c33cb08b48581eed" dependencies: babel-code-frame "^6.22.0" builtin-modules "^1.1.1" @@ -2899,11 +2956,11 @@ tslint@^5.8.0: resolve "^1.3.2" semver "^5.3.0" tslib "^1.8.0" - tsutils "^2.12.1" + tsutils "^2.27.2" -tsutils@^2.12.1: - version "2.26.2" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.26.2.tgz#a9f9f63434a456a5e0c95a45d9a59181cb32d3bf" +tsutils@^2.27.2: + version "2.29.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" dependencies: tslib "^1.8.1" @@ -3035,10 +3092,14 @@ uuid@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" -uuid@^3.0.0, uuid@^3.1.0, uuid@^3.2.1: +uuid@^3.0.0: version "3.2.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" +uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + v8flags@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.0.2.tgz#ad6a78a20a6b23d03a8debc11211e3cc23149477"