The problem with LIFF in Nuxt 3

We recently ran into a problem developing a mobile web app using the LINE Front-end Framework (LIFF). During a test run with sales and design members, the app only worked on devices that had been used in development. On others, LIFF failed to properly initialize. The cause of this behavior seems to be the way Vue Router is configured in Nuxt 3. 

### The goals of this blog are
1. Recreating the liff.init failure 
2. Understanding why liff.init failed 
3. Finding a way to reliably liff.init in a nuxt 3 app

## Liff and nuxt setup

Let's first go over the setup required to recreate the environment in which the problem occurs. 

We need a LIFF ID to call `liff.init` with. We will not cover how to do that now. But you may read [this blog about liff.init](). Understanding the asynchronous authentication process carried out by liff.init is crucial to our goal,

Once we have a LIFF app and ID, we need a Nuxt 3 web app. We go ahead and run this:

  npx nuxi@latest init nuxt-liff-init

We then create a `/pages` directory at the root. In it we make a `index.vue` page and `page2.vue`. This invokes a bit of Nuxt magic that automatically sets up our routing, with `index.vue` as the root page of our app.

To start the app locally, let's run:

  nuxt dev

We also need to link our Nuxt app to our LIFF application. This requires a public https url for Nuxt app. We'll use ngrok to create a tunnel to our localhost and a suitable link. Then to end the setup, in the LINE Developers console, find out LIFF app and under `Endpoint URL`, let's paste the ngrok link starting with `https://`.

Finally, we want some way to see what's going on and debug in our mobile LIFF app. Let's install vconsole.

  yarn add vconsole 

To use it, let's then create a `/plugins` folder and add this JavaScript: 

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

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

## Vue Router in Nuxt 3

Nuxt 3, being built on top of Vue 3, utilizes Vue Router for managing navigation. In traditional Vue applications we have direct control over the entry file like main.js.  This means we execute liff.init and then app.use(router) and app.mount. This also provides a nice opportunity to use liff.init before we initiate our router and app. However, Nuxt 3 abstracts away this setup, meaning we have to time it differently.

As we set up our pages directory before, this is what our routing looks like.

```
nuxt-liff-init
- pages
  |- index.vue
  |- page2.vue
```
## First attempt at liff.init, in index.vue

Let's see what happens when we try to liff.init() in `index.vue`.

```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>
```
Upon opening our LIFF URL from a  chat room, the console shows this:

```
[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
```

Before we event see our console.log for liffInClient, we get a Vue router warning. The warning suggests it is trying to interpret our authentication information as css id selectors, presumably to find some section of the page to scroll to.

Beyond the `liff.isInClient: true` log, we don't see anything. We do not get to see `LIFF initialized successfully` nor `LIFF Initialization failed`. This suggests an issue where the initialization process is disrupted before it can either succeed or fail.

## Initiating liff.init in a plugin 

Like we just did with Vconsole, we can set up a plugin for liff. In Nuxt 3, plugins are custom JavaScript files executed during the initialization phase. We will see if this allows us liff.init without error as well. To do this we use `defineNuxtPlugin`

In `/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);
    });
});
```

Upon opening our LIFF URL from a  chat room, the console shows this:

```
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).
```

As the console result above shows,this method allowed various android and iOS device to execute liff.init successfully. The fact that "liff initiated" logs before the router warning, confirms that premature routing may have been a problem. On some devices, the warning does not appear at all when using this method. However, one iPhone 11 failed to execute liff.init even using this method. The device had not issues opening LIFF apps without nuxt. 

Strangely, I also found that our way of executing liff.init in index.html succeeds on devices after they have once successfully used the plugin method. This reflects our experience described in the introduction. Only devices which had not been used in development failed to execute liff.init during the testing phase.

The conclusion of this blog has to be somewhat disappointing. I was not able to find a 100% reliable way to make liff.init succeed in Nuxt 3 across all devices. If anyone has had more success, I would love to hear how.