2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

CloudFunctionsでFitbitのアクセストークンを自動更新する

Posted at

概要

  • CloudFunctionsを使って、Fitbitのアクティビティデータを取得し続けたい。
  • しかし、データ取得に必要なアクセストークンの有効期限が28,800秒(=8時間)なので、8時間ごとにアクセストークンを更新する仕組みが必要。
  • そのやり方をまとめました。

環境

Windows10
Visual Studio Code(1.73.0)
firebase-tools 11.16.0

前提条件

  • Node.jsのダウンロード・インストール済み

ライブラリインストール

my-project/functions
npm install buffer
my-project/functions
npm install axios
  • Node Fetch
    • GET, POST用
    • 公式ドキュメント https://www.npmjs.com/package/node-fetch
    • Node Fetch v3からrequire('node-fetch')でimportできなくなったので、CloudFunctionsで使う場合はv2をnpm installする必要があります。
my-project/functions
npm install node-fetch@2

実装例(サンプルコード)

my-project/functions/index.js
const functions = require("firebase-functions");
const Buffer = require('buffer').Buffer;
const fetch = require('node-fetch')

// アクセストークンの更新
async function updateAccessToken(refreshToken, id, secret) {
    // 現状のrefresh_token
    const currentRefreshToken = refreshToken

    // clientIdとclientSecretをbase64にエンコード
    const clientId = id
    const clientSecret = secret
    const base64Before = clientId+":"+clientSecret
    const base64After = Buffer.from(base64Before).toString('base64')

    // 新トークン
    let newAccessToken = ""
    let newRefreshToken = ""

    // アクセストークンの更新
    await fetch('https://api.fitbit.com/oauth2/token', {
        method: 'POST',
            headers: {
                'accept': 'application/json',
                'authorization': 'Basic '+base64After,
                'content-type': 'application/x-www-form-urlencoded'
            },
            body: 'grant_type=refresh_token&refresh_token='+currentRefreshToken
        })
        .then(response => {
            return response.json()
        })
        .then(data => {
            // 成功時
            if(data.access_token!==undefined && data.refresh_token!==undefined) {
                console.log("access_tokenの更新 -> 成功")
                newAccessToken = data.access_token
                newRefreshToken = data.refresh_token
                console.log("newAccessToken -> "+newAccessToken)
                console.log("newRefreshToken -> "+newRefreshToken)
            }
    
            // 失敗時
            if(data.success===false) {
                console.log("access_tokenの更新 -> 失敗")
            }
    
        })
        .catch(error => {
            console.log("error: ", error)
        })

        // 取得した新トークン
        const tokenObj = {
            newAccessToken: newAccessToken,
            newRefreshToken: newRefreshToken
        }
        return tokenObj
}

より具体的な実装例

  • Fitbitサーバへアクティビティデータを5分ごとに取得しにいき、アクセストークンが期限切れの場合、アクセストークンを更新し、新規取得したアクセストークンとリフレッシュトークンをFirestoreへ保存するサンプルコードです。
my-project/functions/index.js
const functions = require("firebase-functions");
if (admin.apps.length === 0) {
    admin.initializeApp();
} else {
    console.log(admin.apps)
}
const db = admin.firestore()
const { region } = require("firebase-functions");
const axios = require('axios')
const Buffer = require('buffer').Buffer;
const fetch =  require('node-fetch')

// Fitbitからアクティビティデータを定期的に取得する
exports.getFitbitActivityData = functions.region('asia-northeast1')
    .runWith({ secrets: ["SECRET_CLIENT_ID", "SECRET_CLIENT_SECRET"] })
    .pubsub.schedule('every 5 minutes').onRun(async(context) => {

    // clientIdとclientSecret
    const clientId = process.env.SECRET_CLIENT_ID
    const clientSecret = process.env.SECRET_CLIENT_SECRET
    
    // 現在のアクセストークンをDBから取得
    const docRefAccessToken = db.doc("token/token")
    const tokenDoc = await docRefToken.get()
    const currentAccessToken = tokenDoc.data().access_token
    const currentRefreshToken = tokenDoc.data().refresh_token

    let accessTokenIsValid = true
    // Fitbitアクティビティデータを取得する
    // ここは割愛します
    // ...


    // ...
    // アクセストークンの有効期限が切れていた場合
    accessTokenIsValid = false

    // アクセストークンの更新
    let tokenObj = ""
    if(!accessTokenIsValid) {
        tokenObj = await updateAccessToken(currentRefreshToken, clientId, clientSecret)
    }

    // 取得したアクセストークンをDBへ保存
    const newAccessToken = tokenObj.newAccessToken
    const newRefreshToken = tokenObj.newRefreshToken
    if(newAccessToken !== "" && newRefreshToken !== "") {
        try {
            db.runTransaction(async (transaction) => {
                ////////////////////////////////////////////////////
                // 読み取り
                ////////////////////////////////////////////////////
        
                const docRefToken = db.doc("token/token")
                const tokenDoc = await transaction.get(docRefToken);
        
                ////////////////////////////////////////////////////
                // 書き込み
                ////////////////////////////////////////////////////
        
                transaction.update(docRefToken, {
                    access_token: newAccessToken,
                    refresh_token: newRefreshToken,
                })
        
            });
            console.log("アクセストークンの更新成功")
        } catch (e) {
            console.log("アクセストークンの更新失敗", e);
        }
    }
});


// アクセストークンの更新
async function updateAccessToken(refreshToken, id, secret) {
    // 現状のrefresh_token
    const currentRefreshToken = refreshToken

    // clientIdとclientSecretをbase64にエンコード
    const clientId = id
    const clientSecret = secret
    const base64Before = clientId+":"+clientSecret
    const base64After = Buffer.from(base64Before).toString('base64')

    // 新トークン
    let newAccessToken = ""
    let newRefreshToken = ""

    // アクセストークンの更新
    await fetch('https://api.fitbit.com/oauth2/token', {
        method: 'POST',
            headers: {
                'accept': 'application/json',
                'authorization': 'Basic '+base64After,
                'content-type': 'application/x-www-form-urlencoded'
            },
            body: 'grant_type=refresh_token&refresh_token='+currentRefreshToken
        })
        .then(response => {
            return response.json()
        })
        .then(data => {
            // 成功時
            if(data.access_token!==undefined && data.refresh_token!==undefined) {
                console.log("access_tokenの更新 -> 成功")
                newAccessToken = data.access_token
                newRefreshToken = data.refresh_token
                console.log("newAccessToken -> "+newAccessToken)
                console.log("newRefreshToken -> "+newRefreshToken)
            }
    
            // 失敗時
            if(data.success===false) {
                console.log("access_tokenの更新 -> 失敗")
            }
    
        })
        .catch(error => {
            console.log("error: ", error)
        })

        // 取得した新トークン
        const tokenObj = {
            newAccessToken: newAccessToken,
            newRefreshToken: newRefreshToken
        }
        return tokenObj
}
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?