[ask-tips] TypeScriptでAlexaスキルを実装するときの勘所

公開していたスキルをTypeScriptでフルリプレイスしたので、実装時に気をつけた点などをまとめました。 1: Serverless Frameworkだと比較的簡単に準備ができる TypeScriptはそのまま実行す […]

広告ここから
広告ここまで

目次

    公開していたスキルをTypeScriptでフルリプレイスしたので、実装時に気をつけた点などをまとめました。

    1: Serverless Frameworkだと比較的簡単に準備ができる

    TypeScriptはそのまま実行することができない言語(CSSにおけるSassやLessのような存在)なので、ビルド環境が必要となります。で、だいたいここでWebpackかRollupかnpm scriptかみたいな話になるのですが、Serverless Frameworkの場合は公式のテンプレートがあるので、それを使っちゃうのがてっとり早いです。

    $ sls create -t aws-nodejs-typescript

    ちなみにServerless Frameworkで対話モデルなども全て管理するバージョンもあります。ASK CLIとServerless Framework両方使うのが煩わしいという方は、こちらもよさそうです。参考記事:Serverless FrameworkだけでAlexa Skill開発

    $ sls create -t aws-alexa-typescript

    2: “Alexa APIs for Node.js”を追加しよう

    Alexaでは、リクエストもレスポンスもほぼ決まったフォーマットです。なのでTypeScriptやJavaなどの型がある言語を使うことで、実装時の漏れやミスに気付きやすくなるというメリットがあります。

    そしてTypeScriptでは、npm i -D ask-sdk-modelで”Alexa APIs for Node.js“を追加することで、より詳細な型情報を手に入れることができます。

    型の例:Geolotation

    最近追加された位置情報を取得する機能について見て見ましょう。従来の方法ですと、リクエストの値をCloudWatch Logsやシュミレーターなどで確認しつつ実装するという方法を取られることが多いでしょう。しかしここをみると1発でどういう値が送られてくるかがわかります。

        export interface GeolocationState {
            'timestamp'?: string;
            'coordinate'?: interfaces.geolocation.Coordinate;
            'altitude'?: interfaces.geolocation.Altitude;
            'heading'?: interfaces.geolocation.Heading;
            'speed'?: interfaces.geolocation.Speed;
            'locationServices'?: interfaces.geolocation.LocationServices;
        }

    この辺りはVS CodeやWeb StormなどのIDEを使うとより強力になるでしょう。

    3: RequestHandlerとHandlerInputインターフェイスを活用しよう

    ask-sdk-modelにあるRequestHandlerが非常に便利です。これを使うことで、ハンドラーオブジェクトの実装時にIDEのヘルプを受けやすくなります。

    import * as Ask from 'ask-sdk';
    import RequestHandler = Ask.RequestHandler
    import HandlerInput = Ask.HandlerInput
    
    class LaunchRequest implements RequestHandler {
        canHandle(handlerInput: HandlerInput) {
            return isLaunchRequest(handlerInput)
        }
        handle(handlerInput: HandlerInput) {
            const speechText = [
                'サンプルスキルへようこそ。',
                'なにがしたいですか?'
            ].join('')
            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt('なにがしたいですか?他にやりたいことがなければ、終了といってください')
                .getResponse();
        }
    }
    
    export default new LaunchRequest()
    

    このように書くことで、handleメソッドの戻り値が正しく作れているかの確認や、引数からデータを正しく取り出せているかの確認が行えます。

    4: RequestIntentを個別に使おう

    元のモデルを見るとわかるのですが、デフォルトのHandlerInputインターフェイスは全てのリクエストタイプをサポートします。そのため、一部のインテントにしかないプロパティを使おうとすると、null / undefined時のハンドリングを要求されることになります。「canHandleでフィルタしてるからええやん」という気持ちになりますが、handleの引数のインターフェイスが汎用なので仕方ないです。

    と、いうことで必要に応じてextendしたinterfaceを作っておくと便利です。

    import * as Ask from 'ask-sdk';
    import * as model from 'ask-sdk-model';
    
    export namespace LaunchRequestInterfaces {
        export interface RequestEnvelope extends model.RequestEnvelope {
            'request': model.LaunchRequest
        }
        export interface HandlerInput extends Ask.HandlerInput {
            requestEnvelope: RequestEnvelope;
        }
    }

    これでLaunchRequestInterfaces.HandlerInputを使えば、LaunchRequestであることを前提とした実装が可能になります。

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark