Skip to content

Instantly share code, notes, and snippets.

@tatsuyasusukida
Last active September 14, 2022 06:33
Show Gist options
  • Save tatsuyasusukida/80f6a38b25547d64d4da8fddf4259f21 to your computer and use it in GitHub Desktop.
Save tatsuyasusukida/80f6a38b25547d64d4da8fddf4259f21 to your computer and use it in GitHub Desktop.
❤️ Fitbit Web APIを利用して心拍数の生データを取得する方法

❤️ Fitbit Web APIを利用して心拍数の生データを取得する方法

この記事について

この記事ではNode.jsで Fitbit Web API を利用して心拍数の生データを取得する方法について紹介します。

おおまかな手順

おおまかな手順を下記に示します。

  1. Fitbitのアプリ登録
  2. Webアプリのコーディング
  3. 動作確認

Fitbitのアプリ登録

Fitbit Web APIを利用するにはアプリをFitbitに登録する必要があります。Fitbit SDK のページにアクセスして右上にある Manage > Register An App をクリックします。

Fitbit SDKのページです。ヘッダーナビゲーションにManageリンクがあり、フォーカスするとRegister An Appリンクが表示されます。

「Login」ページが表示されたら「Log In」ボタンをクリックします。

Fitbitのログインページです。Log Inボタンを備えています。

ログインフォームが表示されたら「Continue with Google」ボタンを利用するか、メールアドレスとパスワードを入力してから「Login」ボタンをクリックします。

Fitbitのログインフォームです。Continue with Googleボタン、メールアドレス入力部、パスワード入力部、Loginボタンを備えています。

「Register an application」ページが表示されたら下記の内容を入力してから「Register」ボタンをクリックします。

Fitbitのアプリ登録ページです。Application Name入力部やDescription入力部などを備えています。

「Applications I registered」ページが表示されたら下記の2点を控えます。

  • OAuth 2.0 Client ID
  • Client Secret

Fitbitのアプリ詳細ページです。OAuth 2.0 Client ID表示部や、Client Secret表示部を備えています。

Webアプリのコーディング

下記のコマンドを実行してコーディングの準備をします。

mkdir column-fitbit-api
cd column-fitbit-api
npm init -y
npm install --save dotenv express node-fetch@2
touch .env main.js

.env

エディタで.envを開き、前の手順で控えたOAuth2.0ClientIDとClientSecretをそれぞれFITBIT_CLIENT_IDとFITBIT_CLIENT_SECRETとして入力します。

クリックして.envへ移動

.main

エディタでmain.jsを開き、下記の内容を入力します。

クリックしてmain.jsへ移動

ポイントを下記に示します。

  1. アクセストークンを要求する時に必要となるランダムな文字列verifierを生成しています。
  2. 認可エンドポイント(Fitbitのログインフォーム)へのリダイレクト時に必要となるchallengeを生成しています。なお、challengeはverifierのSHA-256ハッシュの Base64URL です。
  3. /signinへのアクセスを認可エンドポイントへリダイレクトします。クエリ文字列のcode_challengeには先の手順で生成したchallengeを指定します。
  4. /callbackは認可エンドポイントからリダイレクトされる時に指定されます。
  5. アクセストークンを要求しています。リクエストボディのcodeにはクエリ文字列経由で渡される認可コードを指定し、リクエストボディのcode_verifierには先の手順で生成したverifierを指定します。
  6. アクセストークンの取得が失敗した場合、エラーメッセージを表示して処理を中断します。
  7. 心拍データを要求しています。Authorizationヘッダーに先の手順で取得したアクセストークンを指定しています。
  8. 心拍データの取得が失敗した場合、エラーメッセージを表示して処理を中断します。
  9. 心拍データの取得が成功した場合、取得したデータをJSON形式で出力します。Webブラウザで表示した時に改行して表示されるようにContent-Typeとしてtext/plainを指定していますが、本来はapplication/jsonの方が適しています。
  10. Base64URLへ変換する関数です。
  11. バッファのSHA-256ハッシュを計算する関数です。

動作確認

下記のコマンドを実行してWebアプリを起動します。

node -r dotenv/config main.js

Webブラウザで http://localhost:3000/signin にアクセスします。

Webブラウザを起動してアドレスバーに http://localhost:3000/signin と入力しています。

認可エンドポイントへリダイレクトされ、Fitbitの認証フォームが表示されたら「Continue with Google」ボタンを利用するか、メールアドレスとパスワードを入力してから「Login」ボタンをクリックします。

Fitbitの認証フォームです。Continue with Googleボタン、メールアドレス入力部、パスワード入力部、Loginボタンを備えています。

Fitbitの認可フォームが表示されたら心拍数にチェックを入れてから「許可」ボタンをクリックします。

Fitbitの認可フォームです。心拍数チェックボックス、拒否ボタン、許可ボタンなどを備えています。

http://localhost:3000/callback へリダイレクトされ、心拍データがページに出力されます。

Fitbit Web APIを利用して取得した心拍数のJSONデータです。activities-heartやactivities-heart-intradayなどのプロパティを備えています。

おわりに

心拍数の生データ(activities-heart-intraday)を取得するにはFitbitのアプリ登録時にOAuth 2.0 Application TypeをPersonalにする必要があります。私は間違ってServerに設定してしまい期待した通りに動かず小一時間ハマってしまいました。皆さまにおかれましては貴重なお時間を費やさぬようご留意ください。最後までお読みいただきありがとうございました!

FITBIT_CLIENT_ID=1A2B3C
FITBIT_CLIENT_SECRET=1234567890abcdef1234567890abcdef
/node_modules/
/package-lock.json
# Do not ignore package-lock.json other than gist.
const crypto = require('crypto')
const express = require('express')
const fetch = require('node-fetch')
if (require.main === module) {
main()
}
async function main () {
try {
const router = express()
const verifier = base64UrlEncode(crypto.randomBytes(64)) // <1>
const challenge = base64UrlEncode(sha256Hash(Buffer.from(verifier))) // <2>
router.get('/signin', (req, res) => { // <3>
const search = '?' + new URLSearchParams({
'client_id': process.env.FITBIT_CLIENT_ID,
'response_type': 'code',
'code_challenge': challenge,
'code_challenge_method': 'S256',
'scope': 'heartrate',
})
const url = 'https://www.fitbit.com/oauth2/authorize' + search
res.redirect(url)
})
router.get('/callback', async (req, res, next) => { // <4>
try {
const user = process.env.FITBIT_CLIENT_ID
const pass = process.env.FITBIT_CLIENT_SECRET
const credentials = Buffer.from(`${user}:${pass}`).toString('base64')
const tokenUrl = 'https://api.fitbit.com/oauth2/token'
const tokenResponse = await fetch(tokenUrl, { // <5>
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
'client_id': process.env.FITBIT_CLIENT_ID,
'code': req.query.code,
'code_verifier': verifier,
'grant_type': 'authorization_code',
}).toString(),
})
const tokenBody = await tokenResponse.json()
if (tokenBody.errors) { // <6>
console.error(tokenBody.errors[0].message)
res.status(500).end()
return
}
const userId = '-'
const date = 'today'
const detailLevel = '1sec'
const dataUrl = 'https://api.fitbit.com/' + [
'1',
'user',
userId,
'activities',
'heart',
'date',
date,
'1d',
`${detailLevel}.json`
].join('/')
const dataResponse = await fetch(dataUrl, { // <7>
method: 'GET',
headers: {
'Authorization': `Bearer ${tokenBody['access_token']}`,
},
})
const dataBody = await dataResponse.json()
if (dataBody.errors) { // <8>
console.error(dataBody.errors[0].message)
res.status(500).end()
return
}
res.type('text/plain') // <9>
.send(JSON.stringify(dataBody, null, 2))
} catch (err) {
next(err)
}
})
router.listen(3000)
} catch (err) {
console.error(err)
}
}
function base64UrlEncode (buffer) { // <10>
return buffer.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '')
}
function sha256Hash (buffer) { // <11>
const hash = crypto.createHash('sha256')
hash.update(buffer)
return hash.digest()
}
{
"name": "fitbit-web-api",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"dev": "node -r dotenv/config main.js"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"dotenv": "^16.0.0",
"express": "^4.18.1",
"node-fetch": "^2.6.7"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment