Comprehensive (but unofficial) fitbit web-api handler for node-js and browser

This library supports both browser and nodejs.

Install

Install via npm or yarn:

npm install @giveback007/fitbit-api
<or>
yarn add @giveback007/fitbit-api

Polyfills

NodeJs requires fetch polyfills (not required if running in browser).

// Using ES6 modules
import 'cross-fetch/polyfill';

// Using CommonJS modules
require('cross-fetch/polyfill');

global.FormData = require('form-data');

Usage

If a fitbit-user-id isn’t passed it will default to the current logged in user.

import { FitbitApi } from '@giveback007/fitbit-api';

const api = new FitbitApi("<access-token>", "<fitbit-user-id>" || "-");

api.user.getProfile().then(profile => console.log(profile));

Automatic Token Refresh

Passing a function as a third argument will enable automatic token refreshing, the function will be called on "expired_token" or "invalid_token" errors.

If the refresh function (one past in as third argument) fails -> returns an error object from fitbit with "expired_token" or "invalid_token".

If new "<access-token>" is successfully retrieved the api will retry the call it first failed on.

new FitbitApi("<access-token>", "<fitbit-user-id>", async () => {
  const newToken = await /* some code retrieving new access-token */;
  return newToken;
});

List Of Supported Endpoints

https://dev.fitbit.com/build/reference/web-api

Typescript & Intellisense

All data is typed. Api endpoint with more complex interfaces are boiled down to be easier with the use intellisense.

Error Handling

Successful responses are wrapped in a success object:

{
    "type": "SUCCESS",
    "isSuccess": true,
    "code": 200,
    "data": {...},
    "response": Response,
    "headers": {
        "content-type": "application/json; charset=utf-8"
    }
}

And an error response will return an error object:

{
    "type": "ERROR",
    "isSuccess": false,
    "code": 401,
    "error": { "errors": [{...}], "success": false },
    "response": Response,
    "headers": {
        "content-length": "135",
        "content-type": "application/json"
    }
}

Date Handling

When inputting a date you will see AnyDate type. The api will convert any date that is a valid date string, number, Date, and a variety of other inputs to fit what the fitbit api asks.

For any unspecified value, like month, day, hour, etc…, it will default to the lowest. Eg: '2021' -> '2021/01/01'

Today and Yesterday will be the beginning of the day. Eg: 'Today' -> 'Dec 23 2021 00:00:00'

type DateObj = { y: number; m?: number; d?: number, hr?: number, min?: number, sec?: number, ms?: number };
type AnyDate = string | number | Date | 'today' | 'now' | 'yesterday' | DateObj;

const date = 'today' || 'now' || 'yesterday' // -> ✔️
const date = 1640289705866 // -> ✔️
const date = 'Dec 23 2021 15:00:02' || '2021/12/23' || '2021 12 23' || '2021' ... // -> ✔️
const date = { y: 2021 } || { y: 2021, m: 12, d: 23 } ... // -> ✔️
const date = new Date() // -> ✔️
const date = 'invalid date string' // -> ❌ throw new Error('Invalid Date')
const date = NaN // -> ❌ throw new Error('Invalid Date')

Headers

Certain fitbit response headers (such as rate limiting) are unsupported in the browser and therefore are only accessible in nodejs.

Some of these headers are:

  • fitbit-rate-limit-limit
  • fitbit-rate-limit-remaining
  • fitbit-rate-limit-reset

Subscriptions

This can’t be accessed in the browser since it requires passing in headers that the browser doesn’t support.

Make sure to set up subscriber endpoints & list them in fitbit api app credentials https://dev.fitbit.com/apps. To add this to an existing application use the [Edit Application Settings] button.

For more information: https://dev.fitbit.com/build/reference/web-api/developer-guide/using-subscriptions/

getLogList() & Generators

This library uses generators to load .sleep.getLogList() for /sleep/get-sleep-log-list/ and .activity.getLogList() for /activity/get-activity-log-list/, these endpoints limit data at 100 objects per call and return a "next" link.

To make life easier you can keep calling the generator.next() to automatically retrieve the next set of data.

const generator = api.activity.getLogList({ beforeDate: 'now' });
await generator.next();
// ->
{
    value: {
        allData: [{...}] // the full data collection of all .next() calls.
        lastResponse: { // the response from this .next() call.
            "type": "SUCCESS",
            "isSuccess": true,
            "code": 200,
            "data": {...}
        ],
        totalCalls: 1 // the amount of times .next() is called up to this response.
    },
    done: false // `true` indicates there's no more data to retrieve.
}

Example of retrieving all data from a generator:

const sleepFor2021 = await (async () => {
  // gets data starting from 2020-01-01
  const generator = api.sleep.getLogList({ afterDate: '2020' });

  while (true) {
    const { done, value } = await g.next();

    if (value.lastResponse.type === 'ERROR')
      throw new Error('Failed to load Fitbit data');

    if (done)
      return value.allData;
  }
})();

Chart Data

Utilities for simplifying data use in charting.

Here’s an example of using it with react-plotly.js:

const sleepData: Sleep[] = [...];
const { dateMarkers, sleepLevels, ...chartData } = sleepToChartData(sleepData, {
  // (optional) use this to specify the SMA, here is 7-day & 30-day.
  sma: [7, 30],
  // (optional) if not specified will use the oldest date from data.
  startDate: '2021-08-01',
  // (optional) if not specified will use the latest date from data.
  endDate: 'now',
});
const { asleep, deep, rem, light, wake } = sleepLevels;

const smaArr: Plotly.Data[] = chartData.sma.map(({ data, smaN }, i) => ({
  x: dateMarkers,
  y: data,
  name: `${smaN} Days SMA`,
  type: 'scatter',
  mode: 'lines',
  marker: { color: ['green', 'red'][i] },
}));

<Plot
  style={{ width: '100%' }}
  useResizeHandler={true}
  data={[{
    x: dateMarkers,
    y: asleep,
    name: 'Un-categorized Sleep',
    type: 'bar',
    marker: { color: 'green' }
  }, {
    x: dateMarkers,
    y: deep,
    name: 'Deep-Sleep',
    type: 'bar',
    marker: { color: '#0C0458' }
  }, {
    x: dateMarkers,
    y: rem,
    name: 'Rem-Sleep',
    type: 'bar',
    marker: { color: '#094571' }
  }, {
    x: dateMarkers,
    y: light,
    name: 'Light-Sleep',
    type: 'bar',
    marker: { color: '#339BFF' }
  }, {
    x: dateMarkers,
    y: wake,
    name: 'Awake',
    type: 'bar',
    marker: { color: 'orange' }
  },

  // SMA:
  ...smaArr
  ]}

  layout={{
    title: 'Sleep',
    barmode: 'stack',
    xaxis: { range: ['2021-12-1', '2021-12-26'] },
    autosize: true,
  }}
/>

Example Outcome:

Developer Discord

This project is by the MyAlyce team. If you have any questions join us on discord:

Invitation Link, use #fitbit_integration channel for fitbit api specific things.

TODOs

TODO:

GitHub

View Github