[エンジニア向け]LINEミニアプリの代表的な機能を実装/体験してみる

![貼り付けた画像_2022_06_30_8_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/aaf146ac-354e-95d0-8af1-881a362007d0.png)

前回記事からの続きです。

https://qiita.com/stachibana/items/ce52fbcab84ac3f42300

本記事では前回動作確認を行った公式サンプルコードを編集しながらLINEミニアプリの機能を体験すると共に、便利なデバッグツールの使い方についても解説します。

# 本記事の対象者

* 基本的なプログラミングを理解している方
* LINEミニアプリやLIFFを利用した開発が初めて(久しぶり)の方
* [前回記事](https://qiita.com/stachibana/items/ce52fbcab84ac3f42300)で解説したアプリケーションが手元にある方

# 準備

まず、前回のプロジェクトを少し編集します。

## 起動コマンドの変更

前回は公式に習って[Netlify](https://www.netlify.com/)へアプリケーションをデプロイし、そのURLをミニアプリのエンドポイントとして利用しましたが、今回は利便性のためGitpod上のプレビューURLを利用します。

プレビューには`webpack-dev-server`を利用しておりますので、起動時のコマンドを以下のように変更します。

```diff_javascript:package.json
  "scripts": {
-    "dev": "cross-env NODE_ENV=development webpack-dev-server --progress",
+    "dev": "LIFF_ID='前回取得したLIFF ID' cross-env NODE_ENV=development webpack-dev-server --progress",
    "build": "cross-env NODE_ENV=production webpack"
  },
```

## babelの設定変更

続いて、babelの設定変更を行います。これをしておかないと後ほどエラーでビルドに失敗します。

```bash
$ gitpod /workspace/line-liff-v2-starter/src/vanilla (master) $ yarn add --dev @babel/core @babel/preset-env
```

```diff_batchfile:.babelrc
{
    "presets": [
-      "env"
+      ["@babel/preset-env"]
    ]
}
```

## エンドポイントの変更

ではこちらで一度プレビューしてみましょう。

```bash
$ gitpod /workspace/line-liff-v2-starter/src/vanilla (master) $ yarn dev

<w> [webpack-dev-server] "hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration.
<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:3000/
<i> [webpack-dev-server] On Your Network (IPv4): http://10.0.5.2:3000/
<i> [webpack-dev-server] Content not from webpack is served from '/workspace/line-liff-v2-starter/src/vanilla/public' directory
```

エラーなく起動できたらウィンドウ右下の`Ports:3000`、左サイドメニューのブラウザアイコンとクリックし、問題なく起動することを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/2ccef3bd-2a83-6eb7-1e58-b765e1d44723.png)

![貼り付けた画像_2022_06_29_15_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/7dfe7c5d-6392-9267-3dea-0450dbdced47.png)

確認できたら左サイドメニューの鍵アイコンをクリックし、プレビューを公開します。これにより誰にでも見ることができるようになります。

URLをコピーし、LINE DevelopersのLINEミニアプリチャネル > LIFFタブ内のエンドポイント(開発用)に貼り付け、「更新」をクリックします。これでLINEミニアプリはNetlifyにデプロイされたものではなく、Gitpod上のプレビュー状態のアプリケーションにアクセスするようになります。

![貼り付けた画像_2022_06_29_15_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/3ede945b-0682-77cf-a92c-f7d645b5d16f.png)

## LINEミニアプリの設定変更

最後にこの後解説する機能を利用出来るようにするため、ミニアプリの設定を変更します。2つのラジオボタンをオンにしてください。

![貼り付けた画像_2022_06_29_15_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/912e584f-38aa-cb83-7a08-f3afff586035.png)

# プロジェクトの編集

## 画像の追加

`src/vanilla`以下に`public`という名前でフォルダを作成し、以下の画像を追加します。

![header.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/c57d4ff8-b360-0d5d-1a2b-cd546c95c0de.png)

`public/header.png`

![pizza_icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/90e5d3e6-9252-fd59-7490-a370bda07f6b.png)

`public/pizza_icon.png`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/4cc0da98-978f-43e1-c15b-c9edad54e23c.png)


## ソースコードの編集

準備が出来たので、ソースコードを編集していきましょう。HTMLファイルにボタンを追加し、Javascriptでクリックした時に関数を呼び出すようにした単純な変更です。

長いので変更後の全文を掲載します。

```html:index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <link rel="stylesheet" href="index.css" />
  <title>LIFF Starter</title>
</head>

<body>
  <div class="home">
    <h1 class="home__title">
      Welcome to <br /><a class="home__title__link" href="https://developers.line.biz/en/docs/liff/overview/">LIFF
        Starter!</a>
    </h1>
    <div class="home__badges">
      <span class="home__badges__badge badge--primary"> LIFF Starter </span>
      <span class="home__badges__badge badge--secondary"> vanilla </span>
      <span class="home__badges__badge badge--tertiary"> 0.1.0 </span>
      <a href="https://github.com/line/line-liff-v2-starter" target="_blank" rel="noreferrer"
        class="home__badges__badge badge--secondary">
        GitHub
      </a>
    </div>
    <div class="home__buttons">
      <a href="https://developers.line.biz/en/docs/liff/developing-liff-apps/" target="_blank" rel="noreferrer"
        class="home__buttons__button button--primary">
        LIFF Documentation
      </a>
      <a href="https://liff-playground.netlify.app/" target="_blank" rel="noreferrer"
        class="home__buttons__button button--tertiary">
        LIFF Playground
      </a>
      <a href="https://developers.line.biz/console/" target="_blank" rel="noreferrer"
        class="home__buttons__button button--secondary">
        LINE Developers Console
      </a>
      <hr width="100%" />
      <div>
        <img id="profile_img" width="100px" />
        <h2 id="profile_string" />
      </div>
      <hr width="100%" />
      <a id="login" class="home__buttons__button button--primary">
        Login with LINE
      </a>
      <a id="logout" class="home__buttons__button button--tertiary">
        Logout
      </a>
      <a id="stp" class="home__buttons__button button--secondary">
        Share Target Picker
      </a>
      <a id="scan" class="home__buttons__button button--secondary">
        Scan Code
      </a>
      <a id="service_message" class="home__buttons__button button--secondary">
        Service Message
      </a>
      <a id="custom_action" class="home__buttons__button button--secondary">
        Custom Action
      </a>
    </div>
  </div>
</body>

</html>
```

```javascript:index.js
import './index.css';
import liff from '@line/liff'

document.addEventListener("DOMContentLoaded", function () {
    liff
        .init({ liffId: process.env.LIFF_ID })
        .then(() => {
            console.log("Success! you can do something with LIFF API here.")

            document.getElementById('login').addEventListener("click", liff_login);
            document.getElementById('logout').addEventListener("click", liff_logout);
            document.getElementById('stp').addEventListener("click", liff_shareTargetPicler);
            document.getElementById('scan').addEventListener("click", liff_scan);

            document.getElementById('service_message').addEventListener("click", mini_serviceMessage);
            document.getElementById('custom_action').addEventListener("click", mini_customAction);

            if (liff.isLoggedIn()) {
                const idToken = liff.getDecodedIDToken();
                document.getElementById('profile_img').src = idToken.picture
                document.getElementById('profile_string').innerHTML = "こんにちは！" + idToken.name + "さん！"
            } else {
                document.getElementById('profile_string').innerHTML = "ログインされていません。"
            }
        })
        .catch((error) => {
            console.log(error)
        })
});


function liff_login() {
    liff.login()
}

function liff_logout() {
    liff.logout()
    location.reload()
}

function liff_shareTargetPicler() {
    if (liff.isLoggedIn()) {
        liff
            .shareTargetPicker(
                [
                    {
                        type: "text",
                        text: "Hello, World!",
                    },
                ]
            )
    } else {
        alert("shareTargetPickerの利用にはログインが必要です。")
    }
}

function liff_scan() {
    if (liff.isLoggedIn()) {
        liff
            .scanCodeV2()
            .then((result) => {
                alert(result.value)
            })
            .catch((error) => {
                console.log("error", error);
            });
    } else {
        alert("scanCodeV2の利用にはログインが必要です。")
    }
}

function mini_serviceMessage() {
    alert("現在Service Messageの利用には審査承認が必要です。")
}

function mini_customAction() {
    if (liff.isLoggedIn()) {
        liff
            .shareTargetPicker(
                [
                    customActionButtonMessage
                ]
            )
    } else {
        alert("Custom Actionの利用にはログインが必要です。")
    }
}

const customActionButtonMessage = {
    "type": "flex",
    "altText": "代替テキスト",
    "contents":
    {
        "type": "bubble",
        "hero": {
            "type": "image",
            "url": location.href.replace(/\?.*$/,"") + "header.png",
            "size": "full",
            "aspectRatio": "20:13",
            "aspectMode": "cover"
        },
        "body": {
            "type": "box",
            "layout": "vertical",
            "contents": [
                {
                    "type": "box",
                    "layout": "vertical",
                    "contents": [
                        {
                            "type": "text",
                            "text": "Thanks!",
                            "size": "lg",
                            "color": "#000000",
                            "weight": "bold",
                            "wrap": true
                        }
                    ],
                    "spacing": "none"
                },
                {
                    "type": "box",
                    "layout": "vertical",
                    "contents": [
                        {
                            "type": "text",
                            "text": "ご注文を承りました。",
                            "size": "sm",
                            "color": "#999999",
                            "wrap": true
                        }
                    ],
                    "spacing": "none"
                },
                {
                    "type": "box",
                    "layout": "vertical",
                    "contents": [
                        {
                            "type": "box",
                            "layout": "horizontal",
                            "contents": [
                                {
                                    "type": "text",
                                    "text": "ピザ",
                                    "size": "sm",
                                    "color": "#555555",
                                    "wrap": false,
                                    "flex": 3
                                },
                                {
                                    "type": "text",
                                    "text": "× 1",
                                    "size": "sm",
                                    "color": "#111111",
                                    "wrap": false,
                                    "align": "end",
                                    "flex": 1
                                }
                            ],
                            "flex": 1,
                            "spacing": "sm"
                        },
                        {
                            "type": "box",
                            "layout": "horizontal",
                            "contents": [
                                {
                                    "type": "text",
                                    "text": "スパゲッティ",
                                    "size": "sm",
                                    "color": "#555555",
                                    "wrap": false,
                                    "flex": 3
                                },
                                {
                                    "type": "text",
                                    "text": "× 1",
                                    "size": "sm",
                                    "color": "#111111",
                                    "wrap": false,
                                    "align": "end",
                                    "flex": 1
                                }
                            ],
                            "flex": 1,
                            "spacing": "sm"
                        }
                    ],
                    "spacing": "sm",
                    "margin": "lg",
                    "flex": 1
                },
                {
                    "type": "box",
                    "layout": "vertical",
                    "contents": [
                        {
                            "type": "button",
                            "action": {
                                "type": "uri",
                                "label": "ご注文詳細",
                                "uri": "https://liff.line.me/" + process.env.LIFF_ID + "/"
                            },
                            "style": "primary",
                            "height": "md",
                            "color": "#17c950"
                        },
                        {
                            "type": "button",
                            "action": {
                                "type": "uri",
                                "label": "Share",
                                "uri": "https://liff.line.me/" + process.env.LIFF_ID + "/"
                            },
                            "style": "link",
                            "height": "md",
                            "color": "#469fd6"
                        }
                    ],
                    "spacing": "xs",
                    "margin": "lg"
                }
            ],
            "spacing": "md"
        },
        "footer": {
            "type": "box",
            "layout": "vertical",
            "contents": [
                {
                    "type": "separator",
                    "color": "#f0f0f0"
                },
                {
                    "type": "box",
                    "layout": "horizontal",
                    "contents": [
                        {
                            "type": "image",
                            "url": location.href.replace(/\?.*$/,"") + "pizza_icon.png",
                            "flex": 1,
                            "gravity": "center"
                        },
                        {
                            "type": "text",
                            "text": "ほげほげピザ",
                            "flex": 19,
                            "size": "xs",
                            "color": "#999999",
                            "weight": "bold",
                            "gravity": "center",
                            "wrap": false
                        },
                        {
                            "type": "image",
                            "url": "https://vos.line-scdn.net/service-notifier/footer_go_btn.png",
                            "flex": 1,
                            "gravity": "center",
                            "size": "xxs",
                            "action": {
                                "type": "uri",
                                "label": "action",
                                "uri": "https://liff.line.me/" + process.env.LIFF_ID + "/"
                            }
                        }
                    ],
                    "flex": 1,
                    "spacing": "md",
                    "margin": "md"
                }
            ]
        }
    }
}
```

編集できたら、PCのブラウザでURLにアクセスし変更点が反映されているかを確認して下さい。

# 機能の体験

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/9215798b-ce51-33e7-3788-f27517959bbe.png)

では機能を体験してみましょう。

## ログイン/ログアウト

「Login with LINE」ボタンをクリックすると、LINEのメールアドレス/パスワードを利用してサービスにログイン出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/0f283172-55fa-de26-1405-3ad5bd747406.png)

2回目以降は前回のデータが記憶されていますので、より便利です。

![貼り付けた画像_2022_06_30_8_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/bf1b4402-4bb5-a805-af60-82aaba84c74e.png)

ログインが完了すると`liff.getDecodedIDToken()`でユーザーのプロフィールデータが取得出来るようになります。

![貼り付けた画像_2022_06_30_8_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/aaf146ac-354e-95d0-8af1-881a362007d0.png)

なお設定により、メールアドレスも取得可能です。ドキュメントは[こちら](https://developers.line.biz/ja/reference/liff/#get-id-token)。

「Logout」ボタンをクリックするとログアウトできます。

## Share Target Picker

Share Target Pickerは、LINEの友だちやグループにミニアプリ内で生成したメッセージを送信することが出来る機能です。送り主は自分、届くのは友だちとの1:1トークやグループになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/dae99859-5d1d-3500-c6f2-fd3d5292f7de.png)

メッセージはシンプルなテキストから画像、スタンプ、FlexMessageを利用したリッチなものまで、様々なフォーマットが利用出来ます。

Share Target Pickerのドキュメントは[こちら](https://developers.line.biz/ja/reference/liff/#share-target-picker)。

Flex Messageのドキュメントは[こちら](https://developers.line.biz/ja/docs/messaging-api/using-flex-messages/)。

## Scan Code

Scan Codeは二次元バーコードを読み取ることが出来る機能です。スマホだけでなく、PCでもカメラが搭載されていれば利用可能です。

![貼り付けた画像_2022_06_30_8_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/c647facd-f28c-8079-2179-2efee6b74230.png)

ドキュメントは[こちら](https://developers.line.biz/ja/reference/liff/#share-target-picker)。

## Service Message

サービスメッセージはミニアプリから任意のタイミングでユーザーにメッセージを送ることが出来る機能です。Share Target Picker、[LINE公式アカウントのPush Message](https://developers.line.biz/ja/reference/messaging-api/#send-push-message)と比較すると以下のようになります。

||サービスメッセージ|Share Target Picker|Push Message|
|----|----|----|----|
|送り主|ミニアプリ|アプリを開いているユーザー|公式アカウント|
|届く場所|「Service Message」専用の受信トークルーム|ユーザーが選択したトーク/グループ|ユーザー|
|タイミング|ユーザーの操作（アクション）に対する確認や応答であれば任意|即時|任意|
|費用|無料|無料|有料|
|内容の制限|ユーザーの操作（アクション）に対する確認や応答に限る|常識的な範囲で任意(規約による)|常識的な範囲で任意(規約による)|
|利用前の審査|あり|なし|なし|

上記は変更されることもあります。利用の際には[公式](https://developers.line.biz/ja/docs/line-mini-app/discover/custom-features/#service-messages)を参照するように下さい。

なお、Service Messageの利用は現在審査に通過したサービスに限られますので、今回も実装はしてありません。

長くなりますので後日詳細な解説記事をアップする予定です。

## Custom Action Button

カスタムアクションボタンは、ミニアプリを利用しているユーザーが友だちにミニアプリをシェアすることが出来る機能です。

公式のテンプレートやガイドライン、アイコン画像が用意されており、それらを利用して実装する必要があります。ドキュメントは[こちら](https://developers.line.biz/ja/docs/line-mini-app/discover/custom-features/#custom-action-button)

![custom_pizza.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/599942ce-7e8c-c652-d402-52f7f9d06118.png)



`liff.shareTargetPicker()`を実行する際に`index.js`下部の`customActionButtonMessage`のようなオブジェクトを指定し利用します。

## 他

ミニアプリには他の機能もたくさんあります。以下のサイトで挙動が確認できますので、興味のある方は是非ご確認下さい。

[LIFF Playground](https://liff-playground.netlify.app/)

## LINEミニアプリとして起動

これまではGitpodのプレビューURLで挙動を確認してきました。つまり、これまで見ていたのはLINEミニアプリではなく、LIFF SDKを導入したWebアプリケーションと言えます。

LINEミニアプリとしての挙動を確認するにはLINE Developers記載の開発用LIFF URLにアクセスします。

![貼り付けた画像_2022_06_30_12_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/1530d73e-bcae-2d67-64f4-27a1ae33e23c.png)

PCからは見れません。

![貼り付けた画像_2022_06_30_12_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/556b1ee7-befb-a826-f066-26bf4cce403c.png)

スマホから見てみます。

先程までと違い、ログインボタンをクリックせずとも開いたタイミングで自動的にログインされることが確認出来ます。

![IMG_9859.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/499dbe0f-9d84-aa74-4154-daff64a16451.png)

# LIFF Inspectorの紹介

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/c2d0c466-0cc4-a352-fa7b-196b1ffc557f.png)

最後に、LINEミニアプリのデバッグに便利な[LIFF Inspector](https://github.com/line/liff-inspector)をご紹介します。

LIFF Inspectorを利用すると、スマホ上でしか利用できないLINEミニアプリをPC上のChromeの開発者ツールに接続し、コンソール等の便利な機能を使いながら開発を進めることが可能になります。

まずはインストール済みのLIFF SDKのバージョンが2.19.0以上であること確認します。古い場合はアップデートします。

```bash
$ gitpod /workspace/line-liff-v2-starter/src/vanilla (master) $ npm list @line/liff
└── @line/liff@2.16.0

$ gitpod /workspace/line-liff-v2-starter/src/vanilla (master) $ yarn add @line/liff@2.19.0
```

続いて新しいターミナルを立ち上げ、

![貼り付けた画像_2022_06_30_12_37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/6d85d981-fc4c-6ee4-c412-6b6e0b2b06e9.png)

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

```bash
$ cd src/vanilla/
$ gitpod /workspace/line-liff-v2-starter/src/vanilla (master) $ npx @line/liff-inspector

Need to install the following packages:
  @line/liff-inspector
Ok to proceed? (y) y ← yキー押下
Debugger listening on ws://10.0.5.2:9222

You need to serve this server over SSL/TLS
For help, see: https://github.com/line/liff-inspector#important-liff-inspector-server-need-to-be-served-over-ssltls
```

LIFF Inspectorサーバーが9222番で起動しました。3000番と同様に9222番もpublicに設定してください。

ターミナル右側の起動中のターミナル一覧から先程まで使っていたターミナルに戻り、パッケージをインストールします。

```bash
$ gitpod /workspace/line-liff-v2-starter/src/vanilla (master) $ yarn add @line/liff-inspector
```

`index.js`を編集します。

```diff_javascript:index.js
import './index.css';
import liff from '@line/liff'
+ import LIFFInspectorPlugin from "@line/liff-inspector"

document.addEventListener("DOMContentLoaded", function () {
+   liff.use(new LIFFInspectorPlugin())
    liff
        .init({ liffId: process.env.LIFF_ID })
```

起動します。

```bash
$ gitpod /workspace/line-liff-v2-starter/src/vanilla (master) $ yarn dev
```

準備が出来ました。次に、エンドポイントを変更しましょう。これまでのURLの末尾に`?li.origin=wss://9222-[URL]`を付けたものを設定します。

![貼り付けた画像_2022_06_30_13_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/4d061f00-1f7e-da45-79dc-770bab328875.png)

この状態でミニアプリにアクセスすると、LIFF Inspectorを起動したターミナルに開発者ツールのURLが表示されます。

![貼り付けた画像_2022_06_30_13_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/5194f975-5fa7-acbd-e6e0-c51d78b3e0a8.png)

アクセスすると、コンソールにリアルタイムにエラーメッセージ等が出力されているのが確認出来ます。

![貼り付けた画像_2022_06_30_13_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164153/7449f290-8918-43f3-66a3-2fc8a33749c0.png)

これでスマホ上でしか実行出来ないミニアプリをPC上でリアルタイムにデバッグ出来るようになりました。是非開発にお役立て下さい。

# まとめ

本記事ではLINEミニアプリの代表的な機能とその実装サンプルをご紹介しました。

是非既存のWebアプリケーションのLINEミニアプリとしての実装、また新しいLINEミニアプリの創出をお待ちしています。