Framework7のログインコンポーネントを使って、NCMBの認証を良い感じに実装する

Framework7はWeb用のUIフレームワークです。主にモバイルを対象としており、VueやReactなどでも利用できます。

そんなFramework7にはログイン画面というコンポーネントがあるのですが、今回は認証が必要な画面の判定処理を入れつつ、良い感じにルーティングを処理してみました。

## 実際の認証処理

動画にすると以下のようになります。ログインしていなければログイン画面が出て、ログイン完了すると本来表示したかった画面が表示されます。

![ezgif-4-24ec7abba3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/6559de07-c37e-4904-1317-27f291b4161f.gif)


## NCMBの初期化

NCMBのJavaScriptは[ncmb.min.js](https://github.com/NIFCLOUD-mbaas/ncmb_js/blob/master/ncmb.min.js)やnpmなどを使ってインストールできます。

初期化はNCMBで取得できるアプリケーションキー、クライアントキーを使います。

```jsx
const ncmb = new NCMB('APPLICATION_KEY', 'CLIENT_KEY');
```

## Framework7のルーティング

Framework7はパスで指定したルーティングをサポートしています。その中で、 `beforeEnter` キーにて画面遷移する前の判定を入れられます。

```jsx
const routes = [
  {
    path: '/',
    url: './index.html',
  },
  {
    path: '/home/',
    componentUrl: './pages/home.html',
    beforeEnter, // 認証処理
  },
```

`beforeEnter` は以下のように変数を受け取ります。

```jsx
const beforeEnter = async ({resolve, reject, router}) => {
};
```

そのまま画面遷移して良い場合は `resolve()` を呼び、リダイレクトなど別な画面への移動を行う場合には `reject()` を実行します。実際の画面遷移は `router` で指定します。

## beforeEnterでの処理

beforeEnterではNCMBの認証状態をチェックします。もしログイン前だったり、ログイン情報はあるもののセッションの有効期限が切れている場合には認証処理を行います。

`openLoginScreen` がユーザー登録&ログイン処理を行うのですが、ログイン画面が消えるまでをawaitで待っています。こうすることで、リダイレクトのような処理もなく、本来表示したかった画面を表示するだけになります。

```jsx
// 認証が必要な画面にアクセスした時の処理
const beforeEnter = async ({resolve, reject, router}) => {
  // 現在ログインしているユーザー情報を取得
	const currentUser = ncmb.User.getCurrentUser();
	// 認証していない場合はnullになる
	if (!currentUser) {
    // 認証画面を出して、処理が完了するのを待つ
    await openLoginScreen();
    // resolveを実行（元々指定されている画面を表示）
    resolve();
	}
	try {
		// データストアにダミーアクセス
		await ncmb.DataStore('Hello').fetch();
		// アクセスできればresolveを実行（元々指定されている画面を表示）
		resolve();
	} catch (e) {
		// 駄目だった場合は認証情報を削除
		localStorage.removeItem("NCMB/"+ncmb.apikey+"/currentUser");
    // 認証画面を出して、処理が完了するのを待つ
    await openLoginScreen();
    // resolveを実行（元々指定されている画面を表示）
    resolve();
	}
};
```

## openLoginScreenの処理

`openLoginScreen` ではログイン画面の表示と、ログイン画面のイベント処理を行っています。アプリが立ち上がった直後は `app.loginScreen` がないので、それができるまで処理を待ちます。

ログインボタンを押したタイミングで `loginOrSignup` を実行し、その結果がうまくいったらログイン画面を閉じて処理を完了します。

```jsx
// ログイン画面を表示する処理
const openLoginScreen = () => {
  return new Promise((res, rej) => {
    const id = setInterval(() => {
      // ログイン画面の準備ができるのを待つ
      if (!app.loginScreen) {
        return;
      }
      // 準備できていたらインターバル処理を止める
      clearInterval(id);
      // ログイン画面を表示
      app.loginScreen.open('#login-screen');
      // ログイン画面が開いた時の処理
      $('.login-screen').on('loginscreen:opened', (_) => {
        $('.login-screen .login-button').on('click', async (e) => {
          e.preventDefault();
          try {
            // ログイン&新規登録処理
            await loginOrSignup();
            // ログイン画面を閉じる
            app.loginScreen.close();
            res();
          } catch (_) {
            // ログインに失敗した場合はエラーメッセージを表示
            app.toast.create({
              text: 'ログインに失敗しました',
              position: 'center',
              closeTimeout: 2000,
            }).open();
          }
        });
      });
    }, 100);
  });
};
```

## loginOrSignupの処理

`loginOrSignup`はNCMBの認証処理になります。最初にユーザー登録処理を行い、その結果いかんに関わらずログイン処理を行います。もしすでにユーザーがいる場合にはユーザー登録処理はエラーになりますが、そのままログインを行うことでユーザー登録とログイン処理を一つの処理でできます。

```jsx
// ログイン&新規登録処理
const loginOrSignup = async () => {
  // フォームからユーザー名とパスワードを取得
  const params = app.form.convertToData($('.login-screen #login-form'));
  try {
    // ユーザー登録
    const user = new ncmb.User;
    await user
      .set("userName", params.username)
      .set("password", params.password)
      .signUpByAccount();
  } catch (_) {
    // 失敗した場合はすでにユーザーが存在する場合
  }
  // ログイン処理
  await ncmb.User.login(params.username, params.password);
  return true;
};
```

## まとめ

Framework7のログインコンポーネントとNCMBを組み合わせることで、アプリの認証処理を手軽に実装できます。ルーティング処理の部分だけで実現できるので、ぜひトライしてください。

[mBaaSでサーバー開発不要！ | ニフクラ mobile backend](https://mbaas.nifcloud.com/)