【応用編①】LINE Front-end Framework で「ToDo」LINE Botを作る(フロントエンド編)

記事タイトルとURLをコピーする

こんにちは。技術4課の河野です。今回もLINEBotのお話です。
入門編では、以下を紹介してきました。

今回は応用編として、LINE Front-end Framework(以下LIFF) を使ったLINEBotを紹介します。
途中書きながら内容が多くなってきたので、フロントエンド編とバックエンド編の②部構成で説明します。

LINE Front-end Framework とは

LINE Front-end Framework とは、「自作したウェブアプリをLINEのトークルーム上で動作を可能とするプラットフォーム」です。

LIFF SDK を使用することで、「アプリ上でLINEプロフィール情報の取得」や、「トーク画面にメッセージを送信する」なども可能です。

「LINEのユーザー情報を活用したアプリケーション」や、「チャットボットのUIでは対応が困難(メッセージでのやり取りではユーザーのUXが悪い)」の場合は LIFFアプリを検討することをオススメします。

今回作成するもの

Webアプリ入門編では定番のToDoアプリをLIFFアプリで作成し、LINEBotと連携します。
具体的には、LIFFアプリでToDoを作成し、トーク上でToDoを確認するといった感じです。

①:「ToDo作成」と「ToDo一覧」のボタンを表示します。

f:id:swx-go-kawano:20200916170403p:plain

②:「ToDo作成」を押して、LIFFアプリを表示します。

f:id:swx-go-kawano:20200916170424p:plain

③:ToDo名と期限を入力して、追加します。

f:id:swx-go-kawano:20200916170551p:plain

④:「ToDo一覧」を押して、先ほど追加したToDoを確認します。

f:id:swx-go-kawano:20200916170612p:plain

構成

f:id:swx-go-kawano:20200916170825p:plain

構成は大まかに以下3つに分けており、それぞれでデプロイできるようにしています。

  • ①:LIFFアプリ(フロントエンド)
    • S3の静的ホスティングを使用してVue.jsで作成したSPAをホスティングしています。
    • デプロイはCloudFormationのテンプレートを使用しています。
  • ②:LIFFアプリ(バックエンド)
    • LIFFアプリで使用するRESTAPIです。
    • ToDo項目を追加するPOSTメソッドを作成します。
  • ③:Bot(MessagingAPI)
    • Botのバックエンドです。
    • 詳しくは、入門編のブログをご参照ください。

環境

  • macOS(Catalina 10.15.6)
  • @vue/cli 4.5.3
  • aws-cli/1.18.3

LIFFアプリを作る(フロントエンド)

準備

LIFFアプリを作成するためには、事前にLINEログインチャンネルを作成する必要があります。

LINEログインチャンネルはLINE Developersから作成してください。
LINEログインチャンネルのLIFFタブから、チャンネルに LIFF アプリを追加することができます。
※エンドポイントURLは現時点では適当に設定して作成してください

2020年1月までは、Messaging API チャンネルに追加することができたみたいですが、現在はLINEログインチャンネル以外のチャンネルに追加できないようです。

詳細については、以下公式ドキュメントに記載されています。

実装

Vue.js を使用して簡単なToDoシングルページアプリケーションを作成します。

まずは、ディレクトリを準備します。

$ mkdir liff-todo-app
$ cd liff-todo-app

Vue プロジェクトの作成

vue createで Vue プロジェクトを作成します。

$ vue create frontend
? Please pick a preset: Default ([Vue 2] babel, eslint)
.....
🎉  Successfully created project frontend.
👉  Get started with the following commands:

 $ cd frontend
 $ npm run serve

「Successfully」が表示されていることを確認して、 コメントで表示されている通りに、コマンドを入力します

$ cd frontend
$ npm run serve

http://localhost:8080/にアクセスします。

f:id:swx-go-kawano:20200827165009p:plain

上記の画面が開けばOKです。 確認後は、Ctrl + Cで動作を停止しておきます。

ToDo画面を作成

まずは、必要なライブラリをインストールします。

npm install --save @line/liff axios bootstrap-vue

画面レイアウトでBootstrap-vue を使用するために、src/main.jsを開いて、下記のコードを追加します。

import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue)

ToDoコンポーネントを作成します。 src/component/CreateToDo.vueを新規作成し、以下のように修正します。

<template>
    <div>
        <h1> Todo create</h1>
        <b-container fluid>
            <b-form  v-if="show">
                <b-col sm="3">
                    <label>ToDo:</label>
                </b-col>
                <b-col sm="5" style="padding-bottom: 30px">
                    <b-form-input type="text" v-model="form.subject" placeholder="ToDoを入力してください"></b-form-input>
                </b-col>
                <b-col sm="3">
                    <label for="datepicker">期限:</label>
                </b-col>
                <b-col sm="5" style="padding-bottom: 30px">
                    <b-form-datepicker id="datepicker" v-model="form.limit" class="mb-2" ></b-form-datepicker>
                </b-col>
            </b-form>
            <b-button style="margin: 10px" type="submit" variant="primary" @click="todoRegister()">Submit</b-button>
        </b-container>
    </div>
</template>

<script>
import liff from "@line/liff";
import axios from "axios";

export default {
    name: 'CreateToDo',
    data() {
        return {
            form: {
                subject: '',
                limit: ''
                },
                show: true
            }
    },
    created() {
        liff.init(
            {
                liffId: process.env.VUE_APP_LIFF_ID,
            }
        )
    },
    methods: {
        async todoRegister(){
            const token = await liff.getAccessToken();
            try{
                const res = await axios.post(
                    process.env.VUE_APP_REGISTER_API,{
                        params:{
                            channel_id: process.env.VUE_APP_LINE_CHANNEL_ID,
                            access_token: token,
                            subject: this.form.subject,
                            limit: this.form.limit
                        },
                        headers: {
                            'Content-Type': 'application/json'
                        }
                    }
                )
                console.log("API Called");
                console.log(res.data);
                this.final_message = "成功しました";
                alert(this.final_message);
                liff.closeWindow();
            }catch(error){
                if (error.response) {
                this.final_message = `ステータスコード: ${error.response.status}`;
                //リクエストを実行し,サーバーからレスポンスがない場合
                } else {
                    this.final_message = "リクエストに失敗しました。";
                }
                alert(this.final_message);
                liff.closeWindow();
            }
        }
    }
}
</script>

<style scoped>
.todo-form {
    padding-bottom: 30px;
}
</style>

LIFF SDK でユーザー情報を取得し、バックエンドのRESTAPIをコールして 入力されたToDo情報をPOSTしています。

LIFF SDKを使用する上でのポイントは以下②点です。

  • LIFFの初期化
  • LIFFアプリでユーザー情報を使用する

LIFF の初期化

LIFF SDKを使用する際は、必ずユーザーがエンドポイントURLに初めてリダイレクトされたタイミング(1次リダイレクト先URL)で、liff.init()メソッドを実行して初期化を行う必要があります。
これ以外で実行すると、初期化に失敗しLIFFアプリが開けません。

初期化に成功することで、LIFF SDK に用意されている様々なAPIを使用することができます。 詳細は公式ドキュメントに記載されています。

developers.line.biz

LIFFアプリでユーザー情報を使用する

今回は、LINEのユーザーIDを使用して、LIFFアプリとBotでデータを連携しています。

LIFF SDK を使用すれば、LIFFを開いているユーザーのプロフィール情報を取得できますが、取得した情報をそのままサーバに送信するのではなく、token でやりとりする必要があります。

詳細は、公式ドキュメントに記載されています。

developers.line.biz

今回の構成に当てはめると以下のイメージになります。

f:id:swx-go-kawano:20200916174927p:plain

Botからは、WebhookオブジェクトにあるuserIdを使用して、直接DynamoDBのデータを取得していますが、ここは正直怪しいところです...

ToDoコンポーネントを表示する

作成したToDoコンポーネントを表示するために、src/App.vueを開いて以下のように編集します。

<template>
  <div id="app">
    <CreateToDo></CreateToDo>
  </div>
</template>

<script>
import CreateToDo from './components/CreateToDo.vue'

export default {
  name: 'App',
  components: {
    CreateToDo
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

環境変数の設定

.envを新規作成し、以下のように編集します

VUE_APP_LINE_CHANNEL_ID="XXXXXXXX"
VUE_APP_LIFF_ID="XXXXXXX"
VUE_APP_REGISTER_API="XXXXXX"

値は環境に合わせて設定してください。VUE_APP_REGISTER_APIはバックエンドAPIのエンドポイントなので現時点では記入しなくてOKです。

デプロイ

AWSリソースの作成

今回は、Cloudformationテンプレートを使用して、静的ホスティングするためのAWSリソースを作成します。

cfn-template-cloudfront-s3.ymlを新規作成し以下のように編集します。

AWSTemplateFormatVersion: "2010-09-09"
Description: S3 and CloudFront (through OAI)

Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Stage"
        Parameters:
          - Stage

    ParameterLabels:
      Stage:
        default: "dev"

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  Project:
    Type: String
    Default: "liff-todo-app"
  Stage:
    Type: String
    Default: "dev"
    AllowedValues:
      - dev
      - stg
      - prd
  # Enable belows line and related setting when using custom domain name and Custom Certificate
  # Support Cname and Certificate on ACM only
  # CustomDomainName:
  #   Type: String
  #   Default: ""
  # CFSSLCertificateId:
  #   Type: String

# Conditions:

Resources:
  # ------------------------------------------------------------#
  #  S3 Bucket
  # ------------------------------------------------------------#
  # Bucket
  Bucket:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: !Sub "${Stage}-${Project}-${AWS::AccountId}"
      Tags:
        - Key: Project
          Value: !Ref Project
  LoggingBucket:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: !Sub "cflogs.${Stage}-${Project}-${AWS::AccountId}"
      Tags:
        - Key: Project
          Value: !Ref Project
  CloudFrontOriginAccessIdentity:
    Type: "AWS::CloudFront::CloudFrontOriginAccessIdentity"
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: !Sub "access-identity-${Bucket}"

  BucketPolicy:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      Bucket: !Ref Bucket
      PolicyDocument:
        Statement:
          - Action: "s3:GetObject"
            Effect: Allow
            Resource: !Sub "arn:aws:s3:::${Bucket}/*"
            Principal:
              CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId

  # ------------------------------------------------------------#
  #  CloudFront
  # ------------------------------------------------------------#
  CloudFrontDistribution:
    Type: "AWS::CloudFront::Distribution"
    Properties:
      DistributionConfig:
        PriceClass: PriceClass_All
        # Uncomment the belows line when using custom domain Cname
        # Aliases:
        #   - CustomDomainName
        Origins:
          - DomainName: !GetAtt Bucket.RegionalDomainName
            Id: !Sub "S3origin-${Bucket}"
            S3OriginConfig:
              OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}"
        DefaultRootObject: index.html
        DefaultCacheBehavior:
          TargetOriginId: !Sub "S3origin-${Bucket}"
          ViewerProtocolPolicy: redirect-to-https
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          DefaultTTL: 3600
          MaxTTL: 86400
          MinTTL: 60
          Compress: true
          ForwardedValues:
            Cookies:
              Forward: none
            QueryString: false
        ViewerCertificate:
          # Uncomment those below lines to attach custom Certification on ACM
          # SslSupportMethod: sni-only
          # MinimumProtocolVersion: TLSv1.1_2016
          CloudFrontDefaultCertificate: true
          # AcmCertificateArn: !Sub "arn:aws:acm:us-east-1:${AWS::AccountId}:certificate/${CFSSLCertificateId}"
        HttpVersion: http2
        Logging:
          Bucket: !GetAtt LoggingBucket.RegionalDomainName
          IncludeCookies: yes
          Prefix: !Ref Project
        Enabled: true

Outputs:
  #Bucket:
  Bucket:
    Value: !Ref Bucket

  #DistributionID
  DistributionID:
    Value: !Ref CloudFrontDistribution

  #DmainName
  DomainName:
    Value: !GetAtt CloudFrontDistribution.DomainName

以下コマンドを実行します

aws cloudformation deploy --template cfn-template-cloudfront-s3.yml --stack-name liff-todo-app --parameter-overrides Stage={stage} --profile={profile}

ソースを配置

作成したVueプロジェクトをS3にアップロードします。

npm run build
aws s3 cp ./dist/* s3://{your_bucketName}/ --recursive

LIFFアプリを表示する

では、作成したLIFFアプリをLINE上で表示させてみましょう。 まずは、最初に作成したLINEログインチャンネルのLIFFタブのエンドポイントURLにCroudFrontのURLを設定します。

f:id:swx-go-kawano:20200916172456p:plain

LIFFURLをトーク画面上から開くと、トーク画面の下からニョキッとLIFFアプリが表示されます。

今回は、メニューボタンを用意して、ボタンを押すとLIFFアプリが表示されるようにしています。

これは、メッセージアクションのURIアクションを使用して、URLにLIFF URLを設定しています。 URIアクションは、ユーザーに特定のURIにリダイレクトします。

developers.line.biz

実装方法については、バックエンド編で詳しく説明します。

まだ、Bot部分を実装していないので、ひとまずLIFFアプリが表示できるまでを確認します。
まずは、LIFF URLをコピーしてトークに送信してください。
自身が送信したリンクをタップすると、LIFFアプリが表示されるはずです。

さいごに

LIFFアプリを作るのは、普通にWebアプリケーションを開発するのと変わらないのですが、 LIFFアプリをLINEのトーク上で表示したり、ユーザーIdを使用する方法については少しハマったので共有させていただきました。

LINE Developerの公式ドキュメントが大変分かりやすいので是非そちらも一読することをオススメします。

developers.line.biz