Firebase Cloud FunctionsでClova CEK SDK Nodejsを動かしてみた所、一ヵ所つまづきポイントがありましたが、ちゃんと動いたので、まとめました。

手っ取り早いコードだけ知りたい方はこちら

使うもの

Firebase Cloud Functions

いわゆるサーバーレスでAPIを作れるクラウドサービス。
AWSでいうところのAPIGatewayとLambdaが引っ付いているイメージ。

Clova CEK SDK Nodejs

ClovaをNode.jsで操作するためのSDK

事前に必要な作業

  • Firebaseアカウント(Googleアカウント)の作成
  • ローカルPCにNode.js(version8)インストール
    https://nodejs.org/ja/download/
  • LineDeveloper 登録

手順

Firebaseでプロジェクト作成

  1. ログイン プロジェクトを追加
    Firebaseコンソール( https://console.firebase.google.com/u/0/?hl=ja )にログインし、「プロジェクトを追加」を押下。

  2. プロジェクトが作成
    適当なプロジェクト名をつけて「次へ」→「プロジェクトを作成」を押下、しばらく待つとプロジェクトが作成される。

Cloud Functions作成

  1. サイドメニューから「Functions」選択し「使ってみる」を押下。

  2. FirebaseCLIをインストール

    画面指示に従い、ターミナルで以下コマンド実行。
    $ npm install -f firebase-tools

  3. デプロイ

    画面指示に従い、ターミナルで以下コマンド実行。
    $ cd [適用なディレクトリ]
    $ mkdir [プロジェクト名]
    $ cd [プロジェクト名]
    $ firebase init
    Firebaseのどのサービスを利用するか聞かれるので「Functions」を選択。
    プロジェクトの選択が聞かれるので、先ほど作ったプロジェクトを選択。
    使用言語やLintなど聞かれるのでとりあえずデフォルトを選択、しばらく待つと作成される。
    作成されたら、該当ディレクトリ内の/Functions/inde.jsを編集。
    6~8行目のコメントアウトを外し、保存。
    1const functions = require('firebase-functions');
    2
    3// // Create and Deploy Your First Cloud Functions
    4// // https://firebase.google.com/docs/functions/write-firebase-functions
    5//
    6exports.helloWorld = functions.https.onRequest((request, response) => {
    7 response.send("Hello from Firebase!");
    8});
    9
    ターミナルで以下コマンド実行。
    $ firebase deploy

  4. 動作確認
    サイトをリロードすると、ダッシュボード画面が現れ、URLが表示されるので、そこにアクセス。

    「Hello from Firebase!」と表示されればOK。

Clova設定

  1. スキル登録
    Clova Extensions Kit チュートリアルに則ってClova Developer Centerでスキル登録
    https://clova-developers.line.me/guide/#/CEK/Tutorials/Build_Simple_Extension.md#Step2
    ExtensionサーバーのURLは、先ほどFirebaseで設定したURLの「helloWorld」を「clova/clova」に変更したものをセット
    )
    https://us-central1-clova-firebase-001.cloudfunctions.net/helloWorld
    の場合、
    https://us-central1-clova-firebase-001.cloudfunctions.net/clova/clova
    発話モデルも登録
    ただ、今回は特に判定あんまりしないので、適当でOKです。 ビルドまで完了させます。 https://clova-developers.line.me/guide/#/CEK/Tutorials/Build_Simple_Extension.md#Step3

Clova CEK SDK Nodejsをインストールし、コード記述

  1. Clova CEK SDKインストール
    ターミナルで以下コマンド実行。

    cd functions
    npm install https://github.com/TanakaMidnight/clova-cek-sdk-nodejs --save
    なお、なぜ公式のSDKリポジトリを使っていないかは、後述

  2. Firebase Cloud FunctionsのNode.jsdeのバージョンを変更する
    FunctionsのデフォルトのNode.jsが6で、
    サンプルコードにasync/awaitなどを使っており、バージョンが8でないと動かないため、
    FunctionsのNode.jsのバージョンを変更する。
     
    functions/package.jsonを変更。
    3~5行目を追加する。

     1{
     2  "name": "functions",
     3  "engines": {
     4    "node": "8"
     5  },
     6  "description": "Cloud Functions for Firebase",
     7  "scripts": {
     8    "serve": "firebase serve --only functions",
     9    "shell": "firebase functions:shell",
    10    "start": "npm run shell",
    11    "deploy": "firebase deploy --only functions",
    12    "logs": "firebase functions:log"
    13  },
    14  "dependencies": {
    15    "@line/clova-cek-sdk-nodejs": "git+https://github.com/TanakaMidnight/clova-cek-sdk-nodejs.git",
    16    "firebase-admin": "~6.0.0",
    17    "firebase-functions": "^2.0.3"
    18  },
    19  "private": true
    20}

  3. サンプルコードをもとにコード実装
    functions/index.jsを
    公式サンプルコード
    https://github.com/line/clova-cek-sdk-nodejs#example
    から一部変更します。
    (変更した部分はハイライトしてます。)
    なお、45行目の「YOUR_APPLICATION_ID」はClova Developer Centerで登録した、「Extension ID」をセットします。
     
    functions/index.js

     1const functions = require('firebase-functions');
     2
     3const clova = require('@line/clova-cek-sdk-nodejs');
     4const express = require('express');
     5const bodyParser = require('body-parser');
     6
     7const clovaSkillHandler = clova.Client
     8  .configureSkill()
     9  .onLaunchRequest(responseHelper => {
    10    responseHelper.setSimpleSpeech({
    11      lang: 'ja',
    12      type: 'PlainText',
    13      value: 'おはよう',
    14    });
    15  })
    16  .onIntentRequest(async responseHelper => {
    17    const intent = responseHelper.getIntentName();
    18    const sessionId = responseHelper.getSessionId();
    19
    20    switch (intent) {
    21      case 'Clova.YesIntent':
    22        // Build speechObject directly for response
    23        responseHelper.setSimpleSpeech({
    24          lang: 'ja',
    25          type: 'PlainText',
    26          value: 'はいはい',
    27        });
    28        break;
    29      case 'Clova.NoIntent':
    30        // Or build speechObject with SpeechBuilder for response
    31        responseHelper.setSimpleSpeech(
    32          clova.SpeechBuilder.createSpeechText('いえいえ')
    33        );
    34        break;
    35    }
    36  })
    37  .onSessionEndedRequest(responseHelper => {
    38    const sessionId = responseHelper.getSessionId();
    39
    40    // Do something on session end
    41  })
    42  .handle();
    43
    44const app = new express();
    45const clovaMiddleware = clova.Middleware({ applicationId: "YOUR_APPLICATION_ID" });
    46// Use `clovaMiddleware` if you want to verify signature and applicationId.
    47// Please note `applicationId` is required when using this middleware.
    48app.post('/clova', clovaMiddleware, clovaSkillHandler);
    49
    50// Or you can simply use `bodyParser.json()` to accept any request without verifying, e.g.,
    51//app.post('/clova', bodyParser.json(), clovaSkillHandler);
    52
    53exports.clova = functions.https.onRequest(app);
    54

  4. デプロイ

    cd ../
    firebase deploy
    デプロイ時に
    Would you like to proceed with deletion? Selecting no will continue the rest of the deployments.
    と聞かれた場合は、Yを入力。 Firebaseコンソールをリロードし、「clova」関数が出来ていることを確認します。

動作確認

  1. Custom Extensionの表示
    Clova Developer Centerを開き、スキル設定画面を開きます
    https://clova-developers.line.me/cek/#/list
    そして、該当のスキルの「対話モデル」の「修正」を押下します。

  2. テスト
    スキルのCustom Extensionが表示されるので、サイドメニューから、「テスト」を押下すると、 テスト画面が表示されるので「発話内容」に「はい」を入力、「サービスの応答」で「はいはい」と返ってくれば正しく接続されています。

    実機でも同様にスキルを起動後に「はい」と発話すると「はいはい」と返ってくるはずです。

  3. ログ確認
    また、Firebaseコンソールのログ画面より、実行ログを確認することができます。

Forkした理由

firebaseでexpressを使った際の仕様として、requestオブジェクトの値が文字列でなく、自動的にObjectで返ってくるようになってます。
https://firebase.google.com/docs/functions/http-events?hl=ja#read_values_from_the_request

そして、ClovaのSDK側では文字列で受け取る前提の実装となっている為、以下のようなエラーが発生します。

TypeError: Data must be a string or a buffer
    at Verify.update (crypto.js:99:16)
    at checkSignature (/srv/clov.js:647:10)
    at /srv/clov.js:688:21
    at step (/srv/clov.js:54:23)
    at Object.next (/srv/clov.js:35:53)
    at fulfilled (/srv/clov.js:25:58)
    at <anonymous>
    at process._tickDomainCallback (internal/process/next_tick.js:228:7)
その為、Objectで来た場合、Stringに変換するようにしました。具体的には↓な感じ、
https://github.com/TanakaMidnight/clova-cek-sdk-nodejs/commit/0477cda4d5c5e6d1891d0ddf3598eda49aa3cc6b

また、SDK触りたくない場合には、「clovaMiddleware」処理しちゃう前に、
request.bodyの値を変換前のbodyの値であるrequest.rawBodyで上書きしちゃえばいけそうです。
(試してないですが。。。)

以上、Firebase Cloud Functions(Node.js version 8)でClova CEK SDK Nodejsを動かしてみた、でした。