Skip to content

Commit

Permalink
Merge pull request #932 from beaugunderson/withings
Browse files Browse the repository at this point in the history
Adding the Withings connector (with 2 synclets: scale and pressure)
  • Loading branch information
quartzjer committed Mar 21, 2012
2 parents 7612b49 + e20fb0b commit 07fb3e3
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 0 deletions.
57 changes: 57 additions & 0 deletions Connectors/Withings/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module.exports = {
handler: function(host, apiKeys, done, req, res) {
var qs = require('querystring');
var request = require('request');
var url = require('url');
var OAlib = require('oauth').OAuth;

var callbackUrl = host + "auth/withings/auth";

var OA = new OAlib('https://oauth.withings.com/account/request_token',
'https://oauth.withings.com/account/access_token',
apiKeys.appKey,
apiKeys.appSecret,
'1.0',
callbackUrl,
'HMAC-SHA1',
null,
{ Accept: '*/*', Connection: 'close' });

var qs = url.parse(req.url, true).query;

// Second phase, after user authorization
if (qs && qs.oauth_token && req.session.token_secret) {
OA.getOAuthAccessToken(qs.oauth_token, req.session.token_secret, qs.oauth_verifier,
function(error, oauth_token, oauth_token_secret) {
if (error || !oauth_token) {
return done(new Error("oauth failed to get access token"));
}

// Note that we're also grabbing and storing
// the user ID from the queryString
done(null, {
consumerKey: apiKeys.appKey,
consumerSecret: apiKeys.appSecret,
token: oauth_token,
tokenSecret: oauth_token_secret,
userId: qs.userid
});
});

return;
}

// First phase, initiate user authorization
OA.getOAuthRequestToken({ oauth_callback: callbackUrl },
function(error, oauth_token, oauth_token_secret, oauth_authorize_url) {
if (error) {
return res.end("failed to get token: " + error);
}

// Stash the secret
req.session.token_secret = oauth_token_secret;

res.redirect('https://oauth.withings.com/account/authorize?oauth_token=' + oauth_token + '&oauth_callback=' + callbackUrl);
});
}
};
95 changes: 95 additions & 0 deletions Connectors/Withings/lib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// node-oauth doesn't provide a method for created signed requests with
// query string parameters instead of Authorization headers, so this is a hack.
function authQueryStringFromUrl(OA, pi, host, url, path) {
var params = OA._prepareParameters(pi.auth.token, pi.auth.tokenSecret, 'HMAC-SHA1', url);

// Remove the OAuth signature from the parameters (we'll generate it again below)
for (var i in params) {
if (params[i][0] == 'oauth_signature') {
delete params[i];

break;
}
}

// OAuth query string parameters need to be sorted
// alphabetically before the signature is generated.
params = params.sort(function(a, b) {
return a[0] > b[0] ? 1 : a[0] < b[0] ? -1 : 0;
});

var output = [];

// Build the query string
for (var parameter in params) {
output.push(params[parameter][0] + '=' + params[parameter][1]);
}

// Add the signature of the query string to the end of the query string
output.push('oauth_signature=' + OA._encodeData(OA._getSignature('GET',
'http://' + host + path, output.join('&'), pi.auth.tokenSecret)));

return '?' + output.join('&');
}

exports.deviceSync = function(device, pather, querier, arrayer) {
// pi: process info
return function(pi, cb) {
var OAlib = require('oauth').OAuth;
var OA = new OAlib(null, null, pi.auth.consumerKey, pi.auth.consumerSecret, '1.0', null, 'HMAC-SHA1', null);
var http = require('http');

var host = 'wbsapi.withings.net';

var url = 'http://' + host + pather(pi) + querier(pi);

var queryString = authQueryStringFromUrl(OA, pi, host, url, pather(pi));

// Setup our own HTTP request since we're using
// query string authorization
var options = {
host: host,
port: 80,
path: pather(pi) + queryString,
headers: {
'Accept': '*/*',
'Connection': 'close'
}
};

// Send the request
http.get(options, function(res) {
res.setEncoding('utf8');

var data = '';

res.on('data', function(chunk) {
data += chunk;
});

// Once we've got the full response...
res.on('end', function() {
var js;

// Try parsing it...
try {
js = JSON.parse(data);
} catch(E) {
return cb(E);
}

var array = {};

array[device] = arrayer(pi, js);

// And return it using the passed in callback,
// with (optionally) updated auth and config data
cb(null, {
auth: pi.auth,
config: pi.config,
data: array
});
});
});
};
};
21 changes: 21 additions & 0 deletions Connectors/Withings/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"author": "Beau Gunderson <beau@beaugunderson.com>",
"name": "withings",
"description": "Withings",
"version": "0.2.0",
"repository": {
"title": "Withings",
"handle": "withings",
"author": "Beau Gunderson <beau@beaugunderson.com>",
"update": "auto",
"github": "https://github.com/LockerProject/Locker",
"type": "connector",
"static": "false",
"url": ""
},
"engines": {
"node": ">=0.4.9"
},
"dependencies": {},
"devDependencies": {}
}
42 changes: 42 additions & 0 deletions Connectors/Withings/pressure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
var MAX_ITEMS = 50;

exports.sync = require('./lib').deviceSync('pressure', function(pi) {
return '/measure';
}, function(pi) {
if (!pi.config) {
pi.config = {};
}

if (typeof pi.config.pressureStart === 'undefined') {
pi.config.pressureStart = 0;
}

if (pi.config.pressureStart === 0) {
return '?action=getmeas&devtype=4&limit=' + MAX_ITEMS + '&userid=' + pi.auth.userId;
}

return '?action=getmeas&devtype=4&limit=' + MAX_ITEMS + '&offset=' + pi.config.pressureStart + '&userid=' + pi.auth.userId;
}, function(pi, js) {
var items;

if (js.body && js.body.measuregrps) {
items = js.body.measuregrps;
}

if (!js || !items || items.length === 0) {
pi.config.pressureStart = 0;
pi.config.nextRun = 0;

return [];
}

if (items.length < MAX_ITEMS) {
pi.config.pressureStart = 0;
pi.config.nextRun = 0;
} else {
pi.config.pressureStart += MAX_ITEMS;
pi.config.nextRun = -1;
}

return items;
});
42 changes: 42 additions & 0 deletions Connectors/Withings/scale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
var MAX_ITEMS = 50;

exports.sync = require('./lib').deviceSync('scale', function(pi) {
return '/measure';
}, function(pi) {
if (!pi.config) {
pi.config = {};
}

if (typeof pi.config.scaleStart === 'undefined') {
pi.config.scaleStart = 0;
}

if (pi.config.scaleStart === 0) {
return '?action=getmeas&devtype=1&limit=' + MAX_ITEMS + '&userid=' + pi.auth.userId;
}

return '?action=getmeas&devtype=1&limit=' + MAX_ITEMS + '&offset=' + pi.config.scaleStart + '&userid=' + pi.auth.userId;
}, function(pi, js) {
var items;

if (js.body && js.body.measuregrps) {
items = js.body.measuregrps;
}

if (!js || !items || items.length === 0) {
pi.config.scaleStart = 0;
pi.config.nextRun = 0;

return [];
}

if (items.length < MAX_ITEMS) {
pi.config.scaleStart = 0;
pi.config.nextRun = 0;
} else {
pi.config.scaleStart += MAX_ITEMS;
pi.config.nextRun = -1;
}

return items;
});
10 changes: 10 additions & 0 deletions Connectors/Withings/synclets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"mongoId": {
"scale": "grpid",
"pressure": "grpid"
},
"synclets": [
{ "name": "scale", "frequency": 43200, "threshold": 0 },
{ "name": "pressure", "frequency": 43200, "threshold": 0 }
]
}
12 changes: 12 additions & 0 deletions Connectors/Withings/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
var fs = require('fs');
var auth = JSON.parse(fs.readFileSync("../../Me/withings/me.json")).auth;

console.error('auth', auth);

var sync = require(process.argv[2]);

sync.sync({ auth: auth }, function(e, js){
console.error('error', e);

console.error("got js", JSON.stringify(js));
});

0 comments on commit 07fb3e3

Please sign in to comment.