liff.init は Vue の createApp より前で実行しよう
前提
Vue.js (3.x 系)で liff アプリを作成するとき、以下のように liff.init
していました。
<template>
<router-view />
</template>
<script lang="ts">
import liff from '@line/liff/dist/lib';
import { defineComponent, onMounted } from 'vue';
import { useRouter } from 'vue-router';
export default defineComponent({
name: 'RootPage',
setup() {
const router = useRouter();
onMounted(async () => {
await liff.init({ liffId: 'xxxxxxxxx' });
if (!liff.isLoggedIn()) {
await router.push({
name: 'page1',
});
} else {
await router.push({
name: 'page2',
});
}
});
},
});
</script>
通常の動作はこれで問題ありませんが、クエリパラメータを付与して liff アプリに遷移したときに問題が起こりました。
期待していた遷移の挙動
まず liff アプリに下記のようにクエリパラメータを付与してアクセスします。
https://liff.line.me/xxxxxxxxxxxxx?hoge=hoge&fuga=fuga
すると、liff サーバによってエンドポイントに指定していたアドレスにリダイレクトされます。この時、もともと付与されていたクエリパラメータは URL エンコードされた状態で liff.state
というパラメータ内に保持されます。これは、liff アプリにおける1次リダイレクトです。(詳細は公式ドキュメントを参照してください。)
https://myliffapp.com/index.html?liff.state=%3Fhoge%3Dhoge%26fuga%3Dfuga
その後、Vue が起動し、前述した liff.init
が呼ばれます。このとき、内部的にliff.state
パラメータを通常のクエリパラメータとして再解釈した URL にリダイレクトされます。これが、2次リダイレクトにあたります。
https://myliffapp.com/index.html?hoge=hoge&fuga=fuga
実際の挙動
実際にはliff.init
の後、下記のような URL に遷移されてしまいました。
https://myliffapp.com/index.html?hoge
なぜか第1パラメータの hoge のキーだけが残る謎挙動。頭を抱えました。
仮説
VueRouter と liff.init
の両方が URL を管理しようとして衝突しているのではないか、 という仮説を立てました。
ドキュメントにも下記のように記載があります。
liff.init()メソッドが返すPromiseオブジェクトがresolveする前に、サーバーやフロントエンド側の処理などでURLを変更しないようにしてください。
この仮説に基づき、VueRouter が動作し始める前にliff.init
してみました。
結果
下記のように変更したところ、クエリパラメータが期待通りに取れるようになりました。
import liff from '@line/liff/dist/lib';
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
async function main() {
// VueRouter が動き始める前に liff.init する
await liff.init({ liffId: 'xxxxxxxx' });
const app = createApp(App);
app.use(router);
app.mount('#app');
}
main();
<template>
<router-view />
</template>
<script lang="ts">
import liff from '@line/liff/dist/lib';
import { defineComponent, onMounted } from 'vue';
import { useRouter } from 'vue-router';
export default defineComponent({
name: 'Base',
setup() {
const router = useRouter();
onMounted(async () => {
// ここでは liff.init しない!!
// await liff.init({ liffId: 'xxxxxxxxx' });
// この時点で正しく2次リダイレクトされ、クエリパラメータは正常に読み取れる。
if (!liff.isLoggedIn()) {
await router.push({
name: 'page1',
});
} else {
await router.push({
name: 'page2',
});
}
});
},
});
</script>
まとめ
ネット上では onMounted
内で liff.init
している例が散見されるので、同じ部分でハマっている方も多い気がします。react など他のフレームワークでも同様にパスの制御が衝突してしまう可能性があるので、liff.init
のタイミングには注意したほうがよさそうです。
その他
Qiita でも同じ内容で投稿しています。
Discussion