Vue & Firebase - 打造簡易聊天室及登入驗證

本文將使用 VueFire 套件實作一個簡單的聊天室功能,並用 Firebase 達成 Google 帳戶驗證,包含登入及登出功能。

前言


之前有一個想法是要做一個 Chrome Extension,讓使用者可以在當下開啟的 URL 下一起聊天或留下ㄧ些資訊,在沒有要另外寫後端程式的前提下,就想說用 Firebase 實做看看順便當作技術研究囉。

完整程式碼參考: https://github.com/brian90191/Vue-Firebase-Chat

 

建立 Vue 專案


使用 Vue-Cli 建立專案,並安裝 vuetifyfirebasevuefire

$ vue create vue-firebase-chat
$ cd vue-firebase-chat
$ npm install vuetify firebase vuefire

 

新增 Firebase 專案


有 Firebase 帳戶後,在首頁點選新增專案並輸入名稱即可建立專案。

 

建立 Firestore 資料庫


需要使用資料庫來存放聊天的訊息資料,所以建立一個 Firestore 作為資料庫,並設定為「允許」讀寫作業。


當 Firestore 建立好後,就可以新增集合 Collection ,這個集合 ID 會作為之後程式存取、新增、更新資料的主鍵,所以請務必好好命名。

我所規劃的聊天訊息資料格式如下,Author 的內容會由 Google 登入後取得的帳戶資訊得到,Content 為使用者輸入的訊息內容,CreateTime 為訊息被新增的時間。

{
    "Message": {
        "ID_001": {
            "Author": {
                "Uid": "",
                "Name": "",
                "PhotoURL": "",
                "Email": ""
            },
            "Content": "",
            "CreateTime": ""
        }
    }
}

所以就可以依照規劃的格式新增一筆範例文件看看。

新增文件完成後的樣子,接下來就可以開始寫程式跟 Firestore 溝通囉。

 

加入 Vuefire 套件


在 Vue 專案的 main.js 加入 Vuefire 套件

import Vue from 'vue'
import App from './App.vue'
import { firestorePlugin } from 'vuefire'

Vue.use(firestorePlugin)

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

 

初始化 Firebase 設定


建議新增另一隻 js 檔來做 firebase 初始化,這樣未來就可以在其他地方直接 import 使用。

import firebase from 'firebase/app'
import 'firebase/firestore'

// Firebase configuration 
const firebaseConfig = {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: ""
};

// Get a Firestore instance
const firebaseApp = firebase.initializeApp(firebaseConfig)

export const db = firebaseApp

在其他要引用的地方直接 import 並使用 firestore

import { db } from '../db'
const fStore = db.firestore()

Config 是來自於 firebase 的應用程式,所以必須要先在 firebase 上新增應用程式喔。

 

讀取 Firebase的資料


VueFire  主要提供兩種綁定資料的方式。

Declarative binding (聲明式綁定)
import { db } from '../db'

const fStore = db.firestore()

export default {
  data() {
    return {
      messages: [],
    }
  },
  firestore: {
    documents: fStore.collection('Message'),
  },
}
Programmatic binding (編程式綁定)
import { db } from '../db'

const fStore = db.firestore()

export default {
  data() {
    return {
      messages: [],
    }
  },
  mounted: function () {
    this.$bind(
      'messages',
      fStore.collection('Message').orderBy('createTime')
    )
  },
}

差別在於編程式的綁定運用較為靈活,可以在任何需要取得/操控資料的情境下使用,以我的範例來說,我是在 mounted 時去把資料掛起來,所以也可以放到其他生命週期去執行,例如使用 watch  監看使用者的變化,然後去 firebase 重新取得想要的資料。  

watch: {
  userID: {
    immediate: true,
    handler(id) {
      this.$bind('message', fStore.collection('Message'))
    },
  },
},

 

新增資料至 Firebase


使用 Vuefire 新增資料就更簡單了,使用 .add 並插入符合格式的資料即可。

addMessage: function () {
  if (this.inputMessage === '') return

  // Add message to firestore
  fStore.collection('Message').add({
    'author': {
      'uid': this.user.uid,
      'name': this.user.displayName,
      'photoURL': this.user.photoURL,
      'email': this.user.email
    },
    'content': this.inputMessage,
    'createTime': firebase.firestore.Timestamp.fromDate(new Date())
  })
  .then(() => {
    this.inputMessage = ''
  })
}

Firebase 上的 createTime 欄位我是設定為 timestamp 格式,透過 js 可以使用 firebase.firestore.Timestamp.fromDate() 做時間格式化,這裡的 firebase 物件是來自於我在 db.js 匯入的 firebase/app 。

 

而新增資料後照理說要把新的資料也同步呈現於畫面,這部分可以不用擔心,Vuefire 已經預設使用 Firebase 的 onSnapshot() 來做資料的即時更新。

如果不使用 Vuefire 的做法可以參考: https://vuefire.vuejs.org/vuefire/#why
Firebase 針對 onSnapshot 的說明: https://firebase.google.com/docs/firestore/query-data/listen 

 

加入登入驗證功能


使用 Firebase 做都入驗證非常方便快速,因為服務都已經包好好的了 (笑),基本上想的到的登入供應商都已涵蓋,如 Google, Facebook, Twitter, Yahoo, Microsoft, Github ... 等。

以下我會使用 Googe 登入來示範!

 

首先到 Authentication 下的登入方式,把需要的登入服務啟用。

先在  db.js 裡面 import 'firebase/auth' ,接下來在需要作登入的地方就可以接用 db.auth() 來取得 auth 相關功能。

登入使用 signInWithPopup() ,登出則使用 logout() ,並建議在 created 或 mounted 生命週期階段使用 onAuthStateChanged 做權限狀態的判斷。

import firebase from 'firebase/app'
import { db } from '../db'
const fAuth = db.auth()

export default {
  data () {
    return {
      user: {},
      isAuth: false
    }
  },
  created () {
    fAuth.onAuthStateChanged(user => {
      if (user) {
        this.user = user
        this.isAuth = true
      } else {
        this.user = {}
        this.isAuth = false
      }
    })
  },
  methods: {
    login () {
      const authProvider = new firebase.auth.GoogleAuthProvider()
      fAuth.signInWithPopup(authProvider)
        .then(result => {
          this.user = result.user
          this.isAuth = true
        })
        .catch(err => console.error(err))
    },
    logout () {
      fAuth.signOut()
        .then(() => {
          this.user = {}
          this.isAuth = false
        })
        .catch(err => console.log(err))
    }
  }
}

基本上這樣已經接近完成了一個登入的模組,只要在再新增登入及登出按鈕並指派 click 行為就可以完成。

 

不過我在按下登入按鈕時,彈出的的視窗卻得到以下訊息。

這時候只要按下畫面上的 Learn More 連結,在 OAuth 同意畫面上修改應用程式名稱及支援電子郵件即可解決使此問題。

成功登入後,可以在使用者頁籤下看到成功登入過的使用者清單。

在此建議,當系統已經有採用 Firebase 的 Authentication 的登入方式後,應該要把取存資料的規則改為判斷使用者,而非全部開放,以避免非符合驗證的使用者存取,auth.uid  則是上圖使用者的 UID。

設定完成後,程式如果沒有登入就存取資料,則會返回沒有權限錯誤的訊息,系統設計時需要再對沒有權限的流程做一番規劃囉。

 

Reference