LIFFの2次リダイレクトでパラメータが消える問題を解決する方法

## ◾️ はじめに
　LIFF（LINE Front-end Framework）を使用してアプリケーションを開発した際、LIFF URLにパラメータ（例：shopId）を付与して店舗ごとのページを表示する仕組みを実装しました。
 しかし、外部ブラウザで開いた場合、2次リダイレクト時にURLに付与したパラメータが消える という問題が発生しました。
 
例えば、以下のようなURLを使用した場合：
- 与えたURL：`https://liff.line.me/hogehoge/?shopId=1`
- 期待動作：`https://liff.line.me/hogehoge/?shopId=1`に遷移して店舗情報を表示
- 実際の動作（問題発生時）: `https://liff.line.me/hogehoge/`に遷移し、パラメータが消失

この記事では、この問題の原因と、liff.stateを削除し、もともとURLに渡していたパラメータ（shopId）を活かす方法について説明します。

## ◾️ LIFFの2次リダイレクトプロセスについて
　LIFF URLにアクセスしてからLIFFアプリが開くまでの動作は以下の通りです。
| リダイレクト先 | 説明 |
|:-----------|------------|
| 1次リダイレクト　| ユーザーがLIFF URLにアクセスすると、LINEブラウザが state パラメータを自動付与します。これにより、LIFFアプリの準備が行われます。 |
| 2次リダイレクト　| 1次リダイレクト後、LIFFアプリが内部的に設定されたURLにリダイレクトします。| 

## ◾️ liff.stateとは
liff.state は、LINE Front-end Framework (LIFF) アプリケーションで使用される仕組みの一つで、LIFFアプリが開かれた際に、URLに含まれる**stateパラメータ**を取得するために使われます。
 
### **パラメータを設定した場合**
LIFFアプリを開くとき、LINEの内部でURLに**自動的にstateパラメータが付与**されます。

例えば、次のようなURLでLIFFアプリを開こうとした場合：
```
https://liff.line.me/{liffId}?redirectUri=https://example.com
```
LINEはこのURLに対して、次のような stateパラメータを自動的に追加します：
```
https://liff.line.me/{liffId}?redirectUri=https://example.com&state=someState
```
この**stateパラメータ**は、LINEがLIFFアプリのセッションや状態を管理するために付与するものです。

### **liff.stateの役割**
liff.stateは、URLに含まれるstateパラメータの値を取得するために使用されます。
例えば、上記の例では liff.stateを実行すると、`someState`という値が返されます。

今回の事例に戻りますが、liff.stateの役割はパラメータの値を取得するために使用されるのにも関わらず、2次リダイレクトの際にパラメータがなぜ消えてしまうのでしょうか？
この問題の原因と解決方法について以下で解説していきます。

## ◾️問題の原因と解決方法
### 原因
**1. LINEが自動的に追加するliff.stateパラメータの影響**
liff.stateは、LINEがLIFF初期化時にURLに自動で付与するパラメータです。
このパラメータが、元々のクエリパラメータ（例：shopId）を上書きしたり、リダイレクト時に一部のパラメータが削除される原因になることが動作検証で判明しました。

**2. 2次リダイレクト時のパラメータ消失**
LIFFの2次リダイレクトのプロセスでは、LINE内部の処理によってURLがリセットまたは再構築されることがあります。
この際、開発者が設定したクエリパラメータ（shopIdなど）がURLから削除される場合があることがわかりました。

この挙動に関してLINE公式ドキュメントでは明確な説明がないため、仕様か不具合かは定かではありませんが、動作検証の結果としてこの問題を確認しています。
 
### 解決方法
**1. URLからクエリ文字列を取得**
window.location.search を使って、現在のURLからクエリ文字列部分を取得します。
例: `?liff.state=some_state_value&shopId=1`

**2. liff.stateを削除**
.replace('?liff.state=', '') によって、URLに含まれる不要なliff.state部分を削除します。
結果：`shopId=1`のみが残ります。

**3. shopIdを抽出して保持**
URLSearchParamsを使用、クエリ文字列を解析し、shopId の値を抽出します。
取得したshopIdをリアクティブ変数 shopId.valueに格納します。

## ◾️ LIFFのURLにパラメータ追加する実装
　店舗ごとのページを表示するため、LIFF URLに shopId パラメータを付与して使用していました。

例：`https://liff.line.me/hogehoge/?shopId=1`

以下のコードは、以下のコードでは、クエリパラメータを取得してshopIdを利用し、店舗ごとに異なるページを表示する仕組みを実装しています。
### 実装コード例：
```App.vue
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { ref, watch } from 'vue'
import { RouterView } from 'vue-router'

const shopId = ref<number | null>(null)
const route = useRoute()

const fetchShopName = async () => {
  if (!shopId.value) return
  try {
    const response = await getShopName()
    shopName.value = response
    console.log(shopName.value)
  } catch (e) {
    error.value = e
    console.log(e)
  }
}

// initializeLiff関数を削除し、removeLiffState関数を追加
const removeLiffState = () => {
// 現在のURLのクエリ文字列を取得、かつ、shopIdを取り出し、?liff.state= を取り除く
const queryString = decodeURIComponent(window.location.search).replace(
  '?liff.state=',
  ''
)
　 // クエリ文字列を解析
  const queries = new URLSearchParams(queryString)
  // shopId パラメータを取得
  const _shopId = queries.get('shopId')
  // shopId.value に設定する
  shopId.value =_shopId
}

/*
route.query.shopIdを監視
shopIdの値が変化した場合に、shopId.valueを更新
*/
watch(
  () => route.query.shopId,
  (selectedShopId) => {
    if (selectedShopId) {
      shopId.value = Number(selectedShopId)
      fetchShopName()
    } else if (shopId.value) {
      fetchShopName()
    }
  }
)

// コンポーネントがマウントされたタイミングで、removeLiffState関数を実行
onMounted(() => {
  removeLiffState()
})
</script>

<template>
  <Loading v-if="isProcessing" />
  <v-app>
    <RouterView />
  </v-app>
</template>
```

## ◾️ おわりに
　この記事では、LIFFアプリケーションで2次リダイレクト時にパラメータが消えてしまう問題の原因と解決方法について説明しました。
最後まで記事を読んでいただきありがとうございました。