自分用のElectronアプリを複数ユーザ対応にしたはなし


あらすじ

これは、私が作成した自分向け学習支援アプリをFirebase Authenticationを導入して、複数ユーザに対応した話です。

使用技術
electron-vue + Firebase + Element + Bootstrap

原型のアプリのはなし

そもそも作成したきっかけとして、勉強を途中でさぼってしまい効率が悪くなるので、画面上にストップウォッチを表示しようとしたところからです。
次第に機能が増えていき、現在は下記の機能が存在します。

  • ストップウォッチ
  • Todoリスト
  • カレンダー + メモ
  • Markdownエディタ

そして、家・研究室などPC間でデータ共有ができるよう、Firebase realtime databaseを導入しています。

なぜ複数ユーザに対応させたのか

現在就活中の身であり、このアプリの話やGithubのリンクを提出するようになりました。
しかし、万が一コードからビルドされると、データ共有機能によりmarkdownエディタに書かれている赤裸々な就活体験記が丸裸となってしまうため、この度ログイン機能など複数ユーザに対応させるはこびとなりました。
配布したかったとかそういう話ではありません。

変更した点

  • Firebase Authenticationの導入
  • ログイン・サインアップ画面・処理
  • realtime databaseへの参照
  • Firebase realtime databaseのルール

Firebase Authenticationの導入

まずはFirebaseのコンソールにて、メール/パスワードを有効にします。
メールリンクは無効にしています。
(Electronだと他のログイン方法は使えないというWebページがちらほら見えた)

参考リンク [Firebase] Authenticationでメール認証 (Web編) その1

ログイン・サインアップ画面・処理を追加

ログイン・サインアップはほとんど同じ画面なのでログイン画面のみ説明します。

画面の基本的な流れとしては、
1. Email, passwordを記入させる (アドレスの形になっているか、パスワードは6文字以上かなど最低限のvalidate)
2. Firebaseに問い合わせる
3. 正しくログインできた場合、main画面(ストップウォッチなどの画面)へ飛ばす

login.vue
<template>
  <div class="col-10 offset-1 mt-5">
    <h2 class="text-center">Log in</h2>
    <el-form :model="loginForm" :rules="rules" ref="loginForm">
      <el-form-item label="E-mail" prop="email">
        <el-input v-model="loginForm.email"></el-input>
      </el-form-item>
      <el-form-item label="password" prop="password">
        <el-input v-model="loginForm.password" show-password></el-input>
      </el-form-item>
      <div class="text-center">
        <el-form-item>
          <el-button type="primary" @click="submit_login(loginForm)">Log in</el-button>
        </el-form-item>
      </div>
    </el-form>

    <p>if you don't have your account</p>
    <router-link to="signup" class="text-center">
      <el-button size="mini" type="primary">Signup</el-button>
    </router-link>
  </div>
</template>

<script>
  import firebase from 'firebase'
  export default {
    data() {
      return {
        interval_id: undefined,
        loginForm: {
          email: '',
          password: ''
        },
        rules: {
          email: [
            { required: true, message: 'Please input E-mail', trigger: 'blur', pattern: /[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/ }
          ],
          password: [
            { required: true, message: 'Please input password', trigger: 'blur' },
            { min: 6, max: 30, message: 'Length should be more 6', trigger: 'blur' }
          ]
        }
      }
    },

    methods: {
      push_page: function() {
        if (firebase.auth().currentUser) {
          console.log(firebase.auth().currentUser.uid)
          this.$message('login successed')
          this.$router.push('/main')
          clearInterval(this.interval_id)
        }
      },

      submit_login: function() {
        console.log(this.$refs.loginForm)
        this.$refs.loginForm.validate((valid) => {
          if (valid) {
            firebase.auth().signInWithEmailAndPassword(this.loginForm.email, this.loginForm.password).catch(
              function(error) {
                console.log(error.code)
                console.log(error.message)
                alert(error.message)
            })
            this.interval_id = setInterval(function() {
              this.push_page()
            }.bind(this), 500);
          } else {
            this.$message.error('error submit')
            console.log('error submit!!')
            return
          }
        });
      }
    }
  }
</script>

つまったポイントとして、

  • 正しくログインできたかがcallbackで帰ってくるため、遷移先のデータで不整合が起こる場合がある
    • ログイン結果が帰ってくるまでsetInterval()で待機
    • ちなみにmethods内でsetInterval()を用いる際にはbind(this)が必要
  • signInWithEmailAndPassword()catchの中でスコープがおかしい (thisなどが読み込めない)
    • 私はjs詳しくないので原因がわかりません.
    • ログインの可否がスマートに判断できないので上記のsetInterval()を使っています

realtime databaseへの参照を変更

既存のコードでは

markdownEditor.vue
if (process.env.NODE_ENV == 'development') { 
  this.markdownRef = this.database.ref('markdown_dev')
} else {
  this.markdownRef = this.database.ref('markdown')
}

以上のように、自分のdevelopmentか本番かだけを判別していました。
複数ユーザに対応し、万が一たくさんのユーザが使用することを考え、データベースの構成をdevelopment/production配下にuserごとにデータを配置する方式に変更しました。

root
 |- development
    |- user
        |- markdown
        |- taskList
 |- production
    |- user
        |- markdown
        |- taskList

そのため、実際のコードも下記のように変更しました。

markdownEditor.vue
this.uid = firebase.auth().currentUser.uid
if (process.env.NODE_ENV == 'development') {
  this.markdownRef = this.database.ref('development/' + this.uid + '/markdown')
} else {
  this.markdownRef = this.database.ref('production/' + this.uid + '/markdown')
}

Firebase realtime databaseのルール変更

上記の変更に合わせて、デフォルトのままだったルールを下記のように変更しました。

{
  "rules": {
    "development": {
      ".read": true,
      ".write": true
    },
    "production": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"
      }
   }
}

こちらに関しては、複雑なルールが必要になるケースではなかったので簡単でした。

あとがき

今回は既存の自分用Electronアプリを複数ユーザに対応させました。
しかし、万が一採用担当の方がコードからビルドしてしまったときのための措置なので、配布する予定は今の所ありません。
パッケージのアップデートをかけたらUbuntuで動作しなくなったからでもありません

あくまで初学者の考えで組んだコードなので、間違っているところやおかしいところを指摘くださると幸いです。