こんにちは。技術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一覧」のボタンを表示します。
②:「ToDo作成」を押して、LIFFアプリを表示します。
③:ToDo名と期限を入力して、追加します。
④:「ToDo一覧」を押して、先ほど追加したToDoを確認します。
構成
構成は大まかに以下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/
にアクセスします。
上記の画面が開けば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を使用することができます。 詳細は公式ドキュメントに記載されています。
LIFFアプリでユーザー情報を使用する
今回は、LINEのユーザーIDを使用して、LIFFアプリとBotでデータを連携しています。
LIFF SDK を使用すれば、LIFFを開いているユーザーのプロフィール情報を取得できますが、取得した情報をそのままサーバに送信するのではなく、token でやりとりする必要があります。
詳細は、公式ドキュメントに記載されています。
今回の構成に当てはめると以下のイメージになります。
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を設定します。
LIFFURLをトーク画面上から開くと、トーク画面の下からニョキッとLIFFアプリが表示されます。
今回は、メニューボタンを用意して、ボタンを押すとLIFFアプリが表示されるようにしています。
これは、メッセージアクションのURIアクションを使用して、URLにLIFF URLを設定しています。 URIアクションは、ユーザーに特定のURIにリダイレクトします。
実装方法については、バックエンド編で詳しく説明します。
まだ、Bot部分を実装していないので、ひとまずLIFFアプリが表示できるまでを確認します。
まずは、LIFF URLをコピーしてトークに送信してください。
自身が送信したリンクをタップすると、LIFFアプリが表示されるはずです。
さいごに
LIFFアプリを作るのは、普通にWebアプリケーションを開発するのと変わらないのですが、 LIFFアプリをLINEのトーク上で表示したり、ユーザーIdを使用する方法については少しハマったので共有させていただきました。
LINE Developerの公式ドキュメントが大変分かりやすいので是非そちらも一読することをオススメします。