2017年6月12日 星期一

[Vue] 使用 Vue 實做 Facebook 登入(login)

Facebook 登入的功能幾乎是現在每個網站基本必備的,但個人過去沒有實做過,利用這個機會剛好看一下 Facebook 的 API,利用 Vue 實做出一個簡單的登入頁面,並且簡單記錄。

使用 Vue cli

下載 vue-cli 工具
npm install -g vue-cli
執行 vue-cli 幫我們安裝好環境設定,在這裡把 project 名稱設為 simple-facebook-login
vue init webpack <project>
這時候移動到專案資料夾中,就可以看到 vue-cli 已經幫我們設定好很多開發時需要的檔案了:
其中 buildconfig 資料夾中都是一些 vue 和 webpack 的設定檔,有興趣進一步瞭解 vue-cli 的話可以參閱 Vue-cli @ Vue。
接著執行 npm install 來安裝相關套件
安裝完套件後,執行 npm run dev 就可以看到網頁了:
本篇文章使用的是 vue-cli v2.8.2

安裝 SASS/SCSS

在正式開始開發前,因為我個人習慣用 scss ,所以另外會安裝 sass-loadernode-sass 這兩個套件:
npm install sass-loader node-sass
安裝完之後,我們就可以在 .vue 檔中使用 sass/scss 來編輯了,只需要在 <style></style> 中加上 lang=scss 的屬性就可以使用了,另外也可以加上 scoped 這個屬性,確保每一個 .vue 檔所撰寫的 scss 是不會互相干擾的:
<style lang="scss" scoped>
#app {
    font-size: 40px;
}
</style>

註冊新增 fb 應用程式

接著到 Facebook Developer 的網站去新增一個自己的應用程式
寫下你的 app 名稱
接著會進入到「新增商品」的頁面,這裡我們選擇「Facebook 登入」旁邊的開始使用
接著可以點選「快速啟動」:
這裡我們要做的是 web-apps,所以選擇網站:
輸入網站,在這裡因為我們是在本地端做測試,所以網站的部分寫localhost:8080,按下 Save 後就可以繼續:
接著它會提供我們 Facebook 的 JavaScript SDK 讓我們使用,我們在來就要把這段程式碼放到 Vue 當中使用:

在 Vue 中使用 FB sdk

整個 sdk 包含兩個部分:
  • 先看下半段用 () 包住的 function,這是以 IIFE 的方式載入 facebook 的 JavaScript SDK,如果有需要的話,可以把 js.src 中間的語言選項改成繁體中文(zh-TW),預設是英文(en_US)。
  • 程式碼上半部 window.fbAsyncInit 則是非同步的方式初始化 Facebook SDK。

<script>
  window.fbAsyncInit = function() {
    FB.init({
      appId      : '<app-id>',
      cookie     : true,
      xfbml      : true,
      version    : 'v2.8'
    });
    FB.AppEvents.logPageView();   
  };

  (function(d, s, id){
     var js, fjs = d.getElementsByTagName(s)[0];
     if (d.getElementById(id)) {return;}
     js = d.createElement(s); js.id = id;
     js.src = "//connect.facebook.net/zh_TW/sdk.js";
     fjs.parentNode.insertBefore(js, fjs);
   }(document, 'script', 'facebook-jssdk'));
</script>
我們可以把程式碼下半部的 IIFE 直接在 main.js 中載入,或者放在 assets 中另外 import 近來,在這裡為了方便管理,我把它放在 ./src/assets/application.js 中:
// .src/assets/application.js

(function(d, s, id){
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) {return;}
  js = d.createElement(s); js.id = id;
  js.src = "//connect.facebook.net/zh_TW/sdk.js";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
接著記得在 main.js 中去載入這支 sdk:
// .src/main.js
import './assets/application.js'
然後在 App.vuemounted hook 中去初始化這支 sdk:
//  ./src/App.vue

mounted () {
    window.fbAsyncInit = function() {
      FB.init({
        appId      : '<app-id>',
        cookie     : true,
        xfbml      : true,
        version    : 'v2.9'
      });
      FB.AppEvents.logPageView();
      console.log('fbAsyncInit')
    };
}
做完 facebook sdk 初始化之後,就可以開始來用這個 sdk 了。

確認使用者 FB 登入狀態

在使用者進來我們的頁面後,要先確定對方有沒有已經登入 Facebook,可以用 sdk 提供的 FB.getLoginStatus(callback<response>) 這個方法來取得使用者登入的狀態。
FB.getLoginStatus(callback<response>) 這個方法裡面要帶一個 callback function ,並且代入一個參數用來取得使用者登入的狀態,這裡我把 fb 回傳的參數稱作 response,我們一樣把程式寫在 mounted 中:
mounted () {
  // facebook 初始化
  window.fbAsyncInit = function() {
    FB.init({
      appId: '<app-id>',
      cookie: true,
      xfbml: true,
      version: 'v2.9'
    });
    FB.AppEvents.logPageView();

    // Get FB Login Status
    FB.getLoginStatus( response => {
      console.log('res', response)        // 這裡可以得到 fb 回傳的結果
    })
  };
}
在 facebook 回傳的 response 中包含幾個屬性:

{
    status:'connected',
    authResponse:{
        accessToken:'...',
        expiresIn:'...',
        signedRequest:'...',
        userID:'...'
    }
}
其中比較重要的 status 包含幾個狀態:
  • connected: 使用者已登入 FB,且授權你的 app 使用。
  • not_authorized: 使用者已登入 FB,但未授權你的 app 使用。
  • unknown: 使用者沒有登入 FB,或已從你的 app 中登出。
如果你拿不到 authResponse 有可能是使用者沒有登入 FB,或者他尚未授權你的 app 使用。

使用 FB 登入

接著我們要讓使用者透過 FB 登入,這時候會用到 FB.login() 這個方法,在 FB.login(callback<response>, options) 中,一樣可以代入一個 callback function 來取得 response。
後面的 options 則是以物件的方式代入想要取得權限的資料,這裡我們可以使用 scope 取得使用者的 public_profileemail
設定 return_scopestrue 時可以得到被授權的清單,可以在使用者登入的 response 中多得到一個名為 grantedScopes 的屬性,裡面會告訴你授權了哪些可用的項目,例如:“email,public_profile”。
我們把 login 的 function 寫在 Vue 的 methods 中:
//  ./src/App.vue

methods: {
  login () {
    let vm = this
    FB.login(function (response) {
      console.log('res', response)
    }, {
      scope: 'email, public_profile',
      return_scopes: true
    })
  }
}
並且綁在 HTML 的 Login button (@click="login")上:
<!--  ./src/App.vue  -->

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <hello></hello>
    <div class="container">
      <div class="row justify-content-center">
        <div class="col-2">
          <button type="button" class="btn btn-outline-success" @click="login">Login</button>
        </div>
        <div class="col-2">
          <button type="button" class="btn btn-outline-success">Logout</button>
        </div>
      </div>
    </div>
  </div>
</template>
這時候當你點選登入的按鈕時,應該會跳轉到 Facebook ,出現下面的畫面
按下登入後這個視窗就會關閉,然後回到你的 app。
然後 app 就可以取得最新的登入狀態,這時候沒有問題的話應該會是 connected
但是這時候你會發現在回傳的 response 中卻沒有拿到 email,這時候我們需要使用 Graph API 來取得使用者的 email 和其他資訊。

取得使用者資訊

如果我們想要取得使用者的 email 或其他資訊,必須使用 Facebook Graph API,FB.api(path, method, params, callback),Graph API 的基本用法可以用 query 來取得你想要的欄位,在這裡我們想要取得使用者的 name, id, 和 email
FB.api('/me?fields=name,id,email', function (response) {
  console.log('res in getProfile', response)
})
我們把取得使用者資料的部分獨立成一個 funtion,稱作 getProfile,當使用者 login 之後,我們就去執行這個方法:
methods: {
  getProfile () {
    FB.api('/me?fields=name,id,email', function (response) {
      console.log('res in graphAPI', response)
    })
  },
  login () {
    let vm = this
    FB.login(function (response) {
      console.log('res when login', response)
      vm.getProfile()
    }, {
      scope: 'email, public_profile',
      return_scopes: true
    })
  }
}
這時候使用者登入時,我們就會呼叫 getProfile() 這個方法,然後就可以得到和使用者相關的資訊:

FB 登出 APP

在登入之後,我們可以用 FB.logout() 這個方法來登出 app。我們一樣把登出寫成一個方法,並且綁訂在 logout 這個 button 上:
//  ./src/App.vue
logout () {
  let vm = this
  FB.logout(function (response) {
    console.log('res when logout', response)
  })
},
<!-- ./src/App.vue -->
<template>
  <div id="app">
    <img src="./assets/logo.png">
    <hello></hello>
    <div class="container">
        <div class="row justify-content-center">
          <div class="col-2">
            <button type="button" class="btn btn-outline-success" @click="login">Login</button>
          </div>
          <div class="col-2">
            <button type="button" class="btn btn-outline-success" @click="logout">Logout</button>
          </div>
        </div>
    </div>
  </div>
</template>
在使用者按下登出按鈕後,status 就會變成 unknown:

當使用者登入狀態改變時

習慣上我們會多一個方法,這個方法是當使用者狀態改變時,我們就會執行它,這裡把這個方法稱作 statusChangeCallback
我們先在 data 中新增兩筆被 Vue 監控的資料:
function data () {
  return {
    profile: {},
    authorized: false
  }
}
在方法中則增加 statusChangeCallback ,每當使用者登入的狀態變更時,就會執行這個方法:
methods: {
    function statusChangeCallback (response) {
      let vm = this
      if (response.status === 'connected') {
        vm.authorized = true
        vm.getProfile()
      } else if (response.status === 'not_authorized') {
        vm.authorized = false
      } else {
        vm.authorized = false
      }
    }
}

程式碼整理

讓我們完整的整理一下 App.vue 這支程式,首先 Template 的部分:
我們在用 v-if 來判斷使用者目前的登入狀態,如果已經登入,則顯示 v-else 中的 logout button;如果尚未登入,則顯示 login button。
在 login 和 logout button 則分別綁上 @click="login"@click="logout" 的方法:
<template>
  <div id="app">
    <img src="./assets/logo.png">
    <hello></hello>
    <div class="container">
        <div class="row justify-content-center">
          <div class="col-2" v-if="!authorized">
            <button type="button" class="btn btn-outline-success" @click="login">Login</button>
          </div>
          <div class="col-2" v-else>
            <button type="button" class="btn btn-outline-success" @click="logout">Logout</button>
          </div>
        </div>
    </div>
  </div>
</template>
再來是 JS 的部分,我們在 data 的 profile 中會儲存使用者從 FB 登入後回傳的使用者資訊。
  • getProfile: 這裡面用了 Facebook Graph API,另外為了讓資料能夠持續有響應式的變化,我們使用了 vm.$set() (可參考:這個 Vue 的方法為什麼畫面沒有隨資料更新 - Vue 響應式原理)。
  • loginlogout 的方法中,我們都會去呼叫 statusChangeCallback 這個方法,幫助我們判斷使用者目前的登入狀態,並更新 Vue 中的資料。
import Hello from './components/Hello'

export default {
  name: 'app',
  components: {
    Hello
  },
  data () {
    return {
      profile: {},
      authorized: false
    }
  },
  methods: {
    getProfile () {
      FB.api('/me?fields=name,id,email', function (response) {
        vm.$set(vm, 'profile', response)
      })
    },
    login () {
      let vm = this
      FB.login(function (response) {
        vm.statusChangeCallback(response) 
      }, {
        scope: 'email, public_profile',
        return_scopes: true
      })
    },
    logout () {
      let vm = this
      FB.logout(function (response) {
        vm.statusChangeCallback(response)
      })
    },
    statusChangeCallback (response) {
      let vm = this
      if (response.status === 'connected') {
        vm.authorized = true
        vm.getProfile()
      } else if (response.status === 'not_authorized') {
        vm.authorized = false
      } else if (response.status === 'unknown') {
        vm.profile = {}
        vm.authorized = false
      } else {
        vm.authorized = false
      }
    }
  },
  mounted () {
    let vm = this
    
    // facebook 初始化
    window.fbAsyncInit = function() {
      FB.init({
        appId: '135862346985755',
        cookie: true,
        xfbml: true,
        version: 'v2.9'
      });
      FB.AppEvents.logPageView();

      // Get FB Login Status
      FB.getLoginStatus(response => {
        vm.statusChangeCallback(response)
      })
    };
  }
}
當目前為止,你就已經完成了一個基本能夠用來登入和登出 FB 的 app 了。

進一步優化

到上一個步驟其實就已經完成了 Facebook 登入。我們可以進一步把從 facebook 取得的資料,利用 props 代到 hello.vue 這個 component 中:
<!-- ./src/App.vue -->
<hello :profile="profile"></hello>
Hello.vue 中我們可以利用從 Facebook 傳來的資訊取得使用者的大頭照,利用 v-showprofile.name 存在時,就顯示只用者的大頭照:
<!-- ./src/components/Hello.vue -->
<template>
  <div class="hello">
    <img v-show="profile.name" :src="profilePicture" alt="profile" class="profile-picture"/>
    <h1 v-html="msg"></h1>
  </div>
</template>
這裡我們使用 props 來取得從父組件傳進來的資料,在 computed 中利用回傳 msg() 這個資料;另外用 profilePicture() 來取得使用者的大頭照:
export default {
  props: ['profile'],
  name: 'hello',
  computed: {
    msg () {
      if (this.profile.name) {
        return `Welcome <b><i> ${this.profile.name} </i></b> to Vue.js App`
      } else {
        return 'Login Facebook to Enjoy the App'
      }
    },
    profilePicture () {
      return (this.profile.id) ? `https://graph.facebook.com/${this.profile.id}/picture?width=300` : `/static/man.gif`
    }
  }
}

完成作品

一開始進入的畫面長的像這樣子:
當使用者點選 “Login” 後會切換到如下的畫面:

程式碼內容

如果想要檢視完整的程式碼可以到 程式碼內容 @ Github

參考資料

Share:

0 意見:

張貼留言