Skip to content

Commit aa348d0

Browse files
author
gustavo.marin
committed
initial commit
0 parents  commit aa348d0

File tree

7 files changed

+257
-0
lines changed

7 files changed

+257
-0
lines changed

.gitignore

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Created by .ignore support plugin (hsz.mobi)
2+
### Node template
3+
# Logs
4+
logs
5+
*.log
6+
7+
# Runtime data
8+
pids
9+
*.pid
10+
*.seed
11+
12+
# Directory for instrumented libs generated by jscoverage/JSCover
13+
lib-cov
14+
15+
# Coverage directory used by tools like istanbul
16+
coverage
17+
18+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
19+
.grunt
20+
21+
# node-waf configuration
22+
.lock-wscript
23+
24+
# Compiled binary addons (http://nodejs.org/api/addons.html)
25+
build/Release
26+
27+
# Dependency directory
28+
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
29+
node_modules
30+
31+
.idea

README.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Fitbit client
2+
3+
An OAuth 2.0 client to consume [Fitbit's API](http://www.fitbit.com/).
4+
5+
## Usage
6+
7+
### Using an existing user token
8+
9+
```
10+
var FitbitClient = require('fitbit-client-oauth2');
11+
12+
var client = new FitbitClient(<YOUR_FITBIT_API_KEY>, <YOUR_FITBIT_API_SECRET> );
13+
14+
// retrieve previously saved user's token from db
15+
client.setTokens(access_token, refresh_token, expires_in);
16+
17+
client.getTimeSeries().then(function(res) {
18+
19+
console.log('results: ', res);
20+
21+
}).catch(function(err) {
22+
console.log('error getting user data', err);
23+
});
24+
25+
```
26+
27+
### Refreshing an expired user token
28+
29+
```
30+
client.refreshAccessToken().then(function(token) {
31+
32+
// save new token data to db
33+
// do more stuff here.
34+
35+
}).catch(function(err) {
36+
console.log('error refreshing user token', err);
37+
});
38+
39+
```
40+
41+
## TODO
42+
43+
* Implement full OAuth authorization code flow. (now it relays on [passport-fitbit-oauth2](https://github.com/IGZgustavomarin/passport-fitbit-oauth2)).
44+
* Cover more of the Fitbit API endpoints
45+
* Add token expiration event to the client (EventEmitter).
46+
* Implement automatic retries on token expiration errors
47+

package.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "fitbit-client-oauth2",
3+
"version": "1.0.0",
4+
"description": "A fitbit client using OAuth 2.0 authentication.",
5+
"main": "src/index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"repository": {
10+
"type" : "git",
11+
"url" : "https://github.com/IGZgustavomarin/fitbit-client-oauth2.git"
12+
},
13+
"author": "Gustavo Marin",
14+
"license": "MIT",
15+
"dependencies": {
16+
"object-assign": "^3.0.0",
17+
"promise": "^7.0.3",
18+
"request-promise": "^0.4.3",
19+
"simple-oauth2": "^0.2.1"
20+
}
21+
}

src/client.js

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
var oauth2 = require('simple-oauth2');
2+
var Promise = require('promise');
3+
4+
var helper = require('./helpers');
5+
var config = require('./config');
6+
7+
var proto;
8+
9+
var FitbitClient = function(clientId, consumerSecret) {
10+
11+
// Set the client credentials and the OAuth2 server
12+
this.oauth2 = oauth2({
13+
clientID: clientId,
14+
clientSecret: consumerSecret,
15+
site: config.FITBIT_BASE_API_URL,
16+
authorizationPath: config.FITBIT_AUTH_PATH,
17+
tokenPath: config.FITBIT_TOKEN_PATH,
18+
useBasicAuthorizationHeader: true
19+
});
20+
21+
};
22+
23+
proto = FitbitClient.prototype;
24+
25+
proto.setTokens = function(accessToken, refreshToken, expiresIn) {
26+
this.token = this.oauth2.accessToken.create({
27+
access_token: accessToken,
28+
refresh_token: refreshToken,
29+
expires_in: expiresIn
30+
});
31+
};
32+
33+
proto.refreshAccessToken = function(options) {
34+
var _this = this;
35+
options = options || {};
36+
37+
if( !this.token.expired() && !options.forceRefresh) {
38+
return Promise.resolve(this.token);
39+
}
40+
41+
return new Promise(function(resolve, reject) {
42+
43+
_this.token.refresh(function(err, token) {
44+
45+
if (err) {
46+
return reject(err);
47+
}
48+
49+
_this.token = token;
50+
return resolve(token);
51+
});
52+
53+
});
54+
};
55+
56+
proto.getTimeSeries = function(options) {
57+
options = helper.buildTimeSeriesOptions(options);
58+
59+
//TODO: improve this way of getting the token
60+
options.access_token = this.token.token.access_token;
61+
return helper.createRequestPromise(options);
62+
63+
};
64+
65+
proto.getIntradayTimeSeries = function(options) {
66+
options = helper.buildIntradayTimeSeriesOptions(options);
67+
68+
options.access_token = this.token.token.access_token;
69+
return helper.createRequestPromise(options);
70+
};
71+
72+
module.exports = FitbitClient;

src/config.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const FITBIT_BASE_API_URL = 'https://api.fitbit.com';
2+
const FITBIT_AUTH_PATH = '/oauth2/authorize';
3+
const FITBIT_TOKEN_PATH = '/oauth2/token';
4+
5+
module.exports = {
6+
FITBIT_BASE_API_URL: FITBIT_BASE_API_URL,
7+
FITBIT_AUTH_PATH: FITBIT_AUTH_PATH,
8+
FITBIT_TOKEN_PATH: FITBIT_TOKEN_PATH
9+
};

src/helpers.js

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
var assign = require('object-assign');
2+
var request = require('request-promise');
3+
4+
var config = require('./config');
5+
6+
module.exports = {
7+
createRequestPromise: createRequestPromise,
8+
buildTimeSeriesOptions: buildTimeSeriesOptions,
9+
buildIntradayTimeSeriesOptions: buildIntradayTimeSeriesOptions
10+
};
11+
12+
function createRequestPromise(options) {
13+
return request.get({
14+
url: options.url,
15+
method: 'GET',
16+
json: true,
17+
headers: {
18+
Authorization: 'Bearer ' + options.access_token
19+
}
20+
}).then(function(res) {
21+
res.requestedResource = options.resourcePath.replace('/', '-');
22+
return res;
23+
});
24+
}
25+
26+
function buildTimeSeriesOptions(options) {
27+
var url = config.FITBIT_BASE_API_URL + '/1/user/{userId}/{resourcePath}/date/{baseDate}/{period}.json';
28+
29+
options = assign({
30+
userId: '-',
31+
resourcePath: 'activities/steps',
32+
baseDate: 'today',
33+
period: '1d'
34+
}, options);
35+
36+
if (options.endDate) {
37+
delete options.period;
38+
}
39+
40+
options.url = url.replace('{userId}', options.userId)
41+
.replace('{resourcePath}', options.resourcePath)
42+
.replace('{baseDate}', options.baseDate)
43+
.replace('{period}', options.endDate ? options.endDate : options.period);
44+
45+
return options;
46+
}
47+
48+
function buildIntradayTimeSeriesOptions(options) {
49+
var url = config.FITBIT_BASE_API_URL + '/1/user/{userId}/{resourcePath}/date/{startDate}/{endDate}/{detailLevel}{extra}.json';
50+
51+
var extra = '/time/{startTime}/{endTime}';
52+
53+
options = assign({
54+
userId: '-',
55+
resourcePath: 'activities/steps',
56+
startDate: 'today',
57+
endDate: 'today',
58+
detailLevel: '1min'
59+
}, options);
60+
61+
if (options.startTime && options.endTime) {
62+
url.replace('{extra}', extra);
63+
}
64+
65+
options.url = url.replace('{userId}', options.userId)
66+
.replace('{resourcePath}', options.resourcePath)
67+
.replace('{startDate}', options.startDate)
68+
.replace('{endDate}', options.endDate)
69+
.replace('{detailLevel}', options.detailLevel)
70+
.replace('{extra}', '')
71+
.replace('{startTime}', options.startTime)
72+
.replace('{endTime}', options.endTime);
73+
74+
return options;
75+
}

src/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
module.exports = require('./client');

0 commit comments

Comments
 (0)