Nuxt 3でのLIFFの問題点

最近、LINE Front-end Framework（LIFF）を使用してモバイルWebアプリの開発を行っている際に問題が発生しました。営業チームとデザインチームとのテストアップミーティング実行中に、開発に使用したデバイスでのみアプリが動作し、他のデバイスではLIFFが正しく初期化されませんでした。この問題の原因は、Nuxt 3でのVue Routerの設定方法にあるようです。

### このブログの目的
1. liff.initの失敗を再現する
2. liff.initが失敗した理由を理解する
3. Nuxt 3アプリでliff.initを確実に行う方法を見つける

## LiffとNuxtの設定

まず、問題が発生する環境を再現するために必要な設定から見ていきましょう。

LIFF IDを取得し、`liff.init`を呼び出す必要があります。その方法についてはこの記事では触れませんが、[こちらのブログ](https://qiita.com/caslinden/items/c219e584c79f14df0875)でliff.initの非同期認証プロセスについて学ぶことができます。

LIFFアプリとIDを取得したら、次にNuxt 3 Webアプリを設定します。以下のコマンドを実行して進めましょう。

```
npx nuxi@latest init nuxt-liff-init
```
ルートに`/pages`ディレクトリを作成し、そこに`index.vue`ページと`page2.vue`を作ります。これによりNuxtの自動ルーティングが設定され、`index.vue`がアプリのルートページとして設定されます。

アプリをローカルで起動するには、以下を実行します。

```
nuxt dev
```
次に、NuxtアプリをLIFFアプリケーションにリンクする必要があります。これにはNuxtアプリの公開用HTTPS URLが必要です。ngrokを使用してローカルホストへのトンネルを作成し、適切なリンクを得ます。設定の最後に、LINE DevelopersコンソールでLIFFアプリを見つけ、「Endpoint URL」にngrokリンクを貼り付けます。

最後に、モバイルLIFFアプリで何が起こっているかを確認しデバッグする方法が必要です。vconsoleをインストールしましょう。

  yarn add vconsole 

その後、`/plugins`フォルダを作成し、以下のJavaScriptを追加します。

```javascript
import { defineNuxtPlugin } from '#app';
import VConsole from 'vconsole';

export default defineNuxtPlugin(() => {
  new VConsole(); 
});
```

## Nuxt 3のVue Router

Nuxt 3はVue 3をベースにしており、ナビゲーションの管理にVue Routerを利用しています。従来のVueアプリケーションでは、main.jsのようなエントリーファイルを直接制御することができます。これにより、ルーターとアプリを初期化する前にliff.initを使用する機会が得られます。しかし、Nuxt 3はこの設定を抽象化しているため、タイミングを異なる方法で調整する必要があります。

設定したページディレクトリを使って、ルーティングが以下のようになります。
```
nuxt-liff-init
- pages
  |- index.vue
  |- page2.vue
```
## index.vueでのliff.initの初回試行

`index.vue`でliff.init()を試みたところ、以下のようになりました。

```javascript
<script setup lang="ts">
import liff from "@line/liff";

const config = useRuntimeConfig();
const liffId = config.public.liffId as string;


onMounted(async () => {
  try {
    // first let's make sure we are in the LIFF browser
    const liffInClient = liff.isInClient();
    console.log("liff.isInClient:", liffInClient);
    await liff.init({ liffId: liffId })
      .then(() => {
        console.log("LIFF initialized successfully");
      });
  } catch (error) {
    console.error("LIFF Initialization failed", error);
  }
  const logStatus = liff.isLoggedIn();
  console.log("isLoggedIn():", logStatus);
});
</script>

<template><h1>Index Page</h1></template>
```
トークルームからLIFF URLを開くと、コンソールには以下が表示されます。

```
[Vue Router warn]: The selector "#context_token=xxx&access_token=xxx&id_token=xxx&client_id=xxxx&mst_challenge=xxxxxx" is invalid. If you are using an id selector, make sure to escape it. You can find more information about escaping characters in selectors at https://mathiasbynens.be/notes/css-escapes or use CSS.escape (https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape).

liff.isInClient: true
```

LIFFブラウザのログが表示される前に、Vueルーターからの警告が出ます。この警告は、認証情報をCSS IDセレクタとして解釈しようとしていることを示しています。これはページの特定のセクションにスクロールしようとしているだろうと思いました。

liff.isInClient: trueのログの後に、LIFF initialized successfullyやLIFF Initialization failedのログが表示されません。これは初期化プロセスが成功または失敗する前に中断されたことを示しています。

## プラグインでのliff.initの開始

先ほどのVconsoleのように、liff用のプラグинを設定することもできます。Nuxt 3では、プラグインは初期化フェーズ中に実行されるカスタムJavaScriptファイルです。これにより、エラーなくliff.initを行うことができるか試してみましょう。これを行うには`defineNuxtPlugin`を使用します。

`/plugins/liffInit.ts`で：

``` javascript
import liff from "@line/liff";
import { useRuntimeConfig } from '#imports';

export default defineNuxtPlugin(async () => {

  const config = useRuntimeConfig();
  const myLiffId: string = config.public.liffId; 

  await liff
    .init({
      liffId: myLiffId, 
    })
    .then(async () => {
      console.log("liff initiated");
    })
    .catch((err) => {
      console.log(err.code, err.message);
    });
});
```

チャットルームからLIFF URLを開くと、コンソールには以下が表示されます。

```
liff.initiated

[Vue Router warn]: The selector "#context_token=xxx&access_token=xxx&id_token=xxx&client_id=xxxx&mst_challenge=xxxxxx" is invalid. If you are using an id selector, make sure to escape it. You can find more information about escaping characters in selectors at https://mathiasbynens.be/notes/css-escapes or use CSS.escape (https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape).
```

この方法では、さまざまなAndroidおよびiOSデバイスでliff.initが成功裏に実行されました。ルーターの警告が表示される前に「liff initiated」というログが記録されたことは、ルーティングが早すぎる問題があったことを確認します。この方法を使用すると、一部のテスト端末では警告が全く表示されません。しかし、iPhone 11の1台はこの方法を使用してもliff.initを実行できませんでした。そのデバイスはNuxtなしでLIFFアプリを開くのに問題はありませんでした。

不思議なことに、index.htmlでliff.initを実行する方法も、一度プラグイン方式を使用したテスト端末で成功することがわかりました。これは序章で述べた経験を反映しています。開発に使用されていなかった端末のみがliff.initの実行に失敗しました。

このブログの結論はやや残念なものとなります。Nuxt 3で全てのデバイスに対してliff.initを100%確実に成功させる方法は見つかりませんでした。もし他に成功された方がいらっしゃれば、その方法をぜひ教えていただきたいです。