概要
- CloudFunctionsを使って、Fitbitのアクティビティデータを取得し続けたい。
- しかし、データ取得に必要なアクセストークンの有効期限が28,800秒(=8時間)なので、8時間ごとにアクセストークンを更新する仕組みが必要。
- そのやり方をまとめました。
環境
Windows10
Visual Studio Code(1.73.0)
firebase-tools 11.16.0
前提条件
- Node.jsのダウンロード・インストール済み
ライブラリインストール
-
CloudFunctionsフォルダ直下へ以下のライブラリをインストールします。
-
Buffer
- Base64変換用
- 公式ドキュメント https://www.npmjs.com/package/buffer
my-project/functions
npm install buffer
- Axios
- GET, POST用
- 公式ドキュメント https://www.npmjs.com/package/axios
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
実装例(サンプルコード)
- Refresh Tokenの公式ドキュメント にアクセストークンを更新するCURLコマンドが載っています。
- Curl Converter のサイトを使うと、CURLコマンドをnode-fetchやaxiosの書き方へコンバートできるので便利です。
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
}