サンプル2　「Jawbone UP3 ＆ Node.jsで歩数をWebページに表示する」

本記事は下記の続き「サンプル2編」です。
[フィットネストラッカーのまとめと連携方法について](http://qiita.com/kamira0627/private/dd02c1cbee0ecf5c449b)

Node.js＋PassportでフィットネストラッカーのJawbone UP3から日付と歩数を取得し、Webページ上で表示させるといった内容です。

# Jawboneのアカウント登録
[Jawboneのデベロッパー用ページ](https://jawbone.com/up/developer)にアクセスし、
アカウントを作成します。
アカウント作成後、左側メニューの **ACCOUNT** をクリックし、アカウントページへ移動。
**Create APP** をクリックし、以下の内容を登録する。

- Name（アプリ名）
- Description（アプリの概要）    
　サンプルなら適当でも可
- Long Description（アプリの詳細）    
　サンプルなら適当でも可
- Logo（ロゴ）
　適当な画像をアップする。無くても可。
- Application Links（ここ重要）
    - URL（アプリのホームページ）    
    　最初にアクセスするページのURLとか。今回は`http://localhost:8000`
    - Authorization URL    
    　ログイン用URL。今回は `http://localhost:8000/login/jawbone`
    - OAuth Redirect URIs    
    　認証後にリダイレクトさせるURL。今回は`http://localhost:8000/first`
    - Learn More About URL（気にしない。空白）
- Pub/Sub    
　Webhookを使う時に使用するので、今回は **Disabled** にチェックする。

アプリの登録を済ますと、左側メニューの **APPLICATIONS**にアプリ名が表示される。
そこから、アプリの管理画面に入れるので、下の値をメモする。
後々、Jawboneのデータを取得するのに必要。

-  Client Id
-  App Secret

# Node.jsの準備
記事のタイトルにもありますが、Jawboneとの連携はNode.jsで行います。
ここで、Node.jsがパソコンに入ってないです! という方は、
下記のURLの方の記事を参考に環境を整えて下さい。
[node.jsインストール備忘録(windows7)](http://qiita.com/yokoh9/items/20d6bdc6030a3a861189)
[macでNode.jsの環境設定](http://qiita.com/tagosaku324/items/bf1fe149c38c99728c72)

今回筆者の環境は以下です。
Windows10 64bit
Node.js **v4.5.0**

作業用のフォルダは任意の場所に以下の通りに作成します。
（※太字がフォルダ）

- **JawboneTest/**
    - **node_modules/**
    - **views/**
        - layout.ect
        - index.ect
        - userdata.ect
    - .env
    - app.js
    - package.json

node_modules には必要なモジュールが入ります。
.ect はhtmlのようなもの。.env は環境変数を書いとくファイル。
※.envはwindowsのファイルエクスプローラー上で作成すると、
名前が入力されていないといったエラーで作成できないかもしれない。
その場合は、普段使用ているエディターで新規作成、保存してください。
app.js がメインのプログラム。
package.json はプロジェクトに必要なモジュールを管理するファイル。

# 必要なモジュールの準備
この後の記事でいくつかモジュールを使うので、ここではその準備をします。
モジュールのインストールには**npm**を利用するのですが、
Node.jsをインストールしたら一緒に入るので安心して下さい。
さて、先程package.jsonというファイルを作成しましたが、
この中身を以下のようにします。

~~~package.json
{
	"name": "JawboneTest",
	"version": "0.0.0",
	"main": "app.js",
	"dependencies": {
		"body-parser": "~1.15.2",
		"cookie-parser":"~1.4.3",
		"ect":"~0.5.9",
		"dotenv":"~2.0.0",
		"express": "~4.14.0",
		"express-session":"~1.14.2",
		"jawbone-up":"~0.1.2",
		"passport":"~0.3.2",
		"passport-oauth":"~1.0.0"
	}
}
~~~

主要なのだけ説明すると
dtenv は先程作成した .envファイルから環境変数を呼び出すため。
express はwebフレームワーク。
ect はテンプレートエンジンといわれるもの。
express-session はexpressでセッションを利用するためのもの。
jawbone-up はJawbone APIを簡単に行うためのもの。
passport はJawboneと連携するための認証を簡単にするためのもの。

package.json を書いたら、app.jsがあるフォルダを開いて、
アドレスバーに `cmd` と記入してEnter。
そのディレクトリに移動した状態でコマンドプロンプトが表示されるので、
`npm install`
と記入します。
先程 package.json の dependencies に記入したモジュールと
それに関連したモジュールが一気に node_modules に入ります。
Node.jsのバージョンが筆者と同じなら、全て入ると思います。

# やっとこ本編
まず最初に、メインのプログラムである app.js の中身を
全て下記に記します。

~~~app.js
var express = require('express'),
    path = require('path'),
    cookieParser = require('cookie-parser'),
    bodyParser = require('body-parser'),
    dotenv = require('dotenv'),
    passport = require('passport'),
    session = require('express-session'),
    JawboneStrategy = require('passport-oauth').OAuth2Strategy,
    ECT = require('ect');

dotenv.load();

var app = express();

var ectRenderer = ECT({ watch: true, root: __dirname + '/views', ext : '.ect' });
app.engine('ect', ectRenderer.render);
app.set('view engine', 'ect');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());

app.set('port', process.env.PORT || 8000);

app.use(session({
    secret: 'jaw key'
}));
app.use(passport.initialize());
app.use(passport.session());

var jawboneAuth = {
    authorizationURL: 'https://jawbone.com/auth/oauth2/auth',
    tokenURL: 'https://jawbone.com/auth/oauth2/token',
    callbackURL: 'http://localhost:8000/first'
};

passport.use('jawbone', new JawboneStrategy({
    clientID: process.env.JAWBONE_CLIENT_ID,
    clientSecret: process.env.JAWBONE_CLIENT_SECRET,
    authorizationURL: jawboneAuth.authorizationURL,
    tokenURL: jawboneAuth.tokenURL,
    callbackURL: jawboneAuth.callbackURL
}, function(token, refresh, profile, done){
    console.log("Get token");
    var user = {
        accessToken: token,
        refreshToken: refresh
    };
    done(null, user);
}));

passport.serializeUser(function(data, done){
    done(null, data);
});

passport.deserializeUser(function(data, done){
    done(null, data);
});

var sessionCheck = function(req, res, next){
    if(req.isAuthenticated())
        return next();
        res.redirect('/login/jawbone');
};

app.get('/', function(req, res) {
    if(!req.isAuthenticated()){
        res.render('index', { title: 'Home' });
        console.log("Home");
    }
    else{
        res.redirect('/data');
    }
});

app.get('/first',
    passport.authenticate('jawbone',{
        scope:['basic_read', 'move_read'],
        failureRedirect:'/'
    }), function(req, res){
        res.redirect('/data');
        console.log("logged in");
    }
);

app.get('/login/jawbone',
    passport.authenticate('jawbone',{
        scope:['basic_read', 'move_read'],
        failureRedirect:'/'
    }), function(req,res){
});

app.get('/data', sessionCheck, function(req, res){
    var options = {
        access_token: req.session.passport.user.accessToken,
        client_id: process.env.JAWBONE_CLIENT_ID,
        client_secret: process.env.JAWBONE_CLIENT_SECRET
    };
    var up = require('jawbone-up')(options);

    up.moves.get({}, function(err, data){
        if(err){
            console.log("Error receiving move data");
        }
        else{
            var jawboneData = JSON.parse(data).data;

            for(var i=0; i<jawboneData.items.length; i++){
                console.log(jawboneData.items[i].date + " : " + jawboneData.items[i].title);
            }
            var sendData = JSON.stringify(jawboneData);
            res.render('userdata', {title: 'User data', data: sendData});
        }
    });
});

app.get('/logout', function(req,res){
    req.logout();
    res.redirect('/');
});

//Open server
var server = app.listen(app.get('port'), function() {
    console.log('Server listening on port ' + app.get('port'));
});
~~~
***

主な箇所を順番に説明すると、
モジュールをrequireして変数に入れることで、この変数名でモジュールが使えるようになります。

~~~js
var express = require('express'),
    cookieParser = require('cookie-parser'),
    bodyParser = require('body-parser'),
    dotenv = require('dotenv'),
    passport = require('passport'),
    session = require('express-session'),
    JawboneStrategy = require('passport-oauth').OAuth2Strategy,
    ECT = require('ect');
~~~
***
これで .env に記述した環境変数が使えるようになります。

~~~js
dotenv.load();
~~~
.env に記述する環境変数はJawboneのアプリ登録を行った際にメモした

-  Client Id
-  App Secret

です。こういったIDとかPassword的な値を直接 app.js に書き込むのは良くないので、.env内に以下のように記述して分けておきます。

~~~　.env　
JAWBONE_CLIENT_ID="自分のID"
JAWBONE_CLIENT_SECRET="自分のSecret"
~~~
app.js内で以下のように記述すると値を取得できます。

~~~app.js
process.env.JAWBONE_CLIENT_ID
process.env.JAWBONE_CLIENT_SECRET
~~~

***
ここでは、テンプレートエンジンを使えるようにするための設定をしています。
今回は、処理スピードが爆速だと一時期話題になっていた **ECT** を使用していますが、他に ejs や jade といったものもあります。

~~~js
var ectRenderer = ECT({ watch: true, root: __dirname + '/views', ext : '.ect' });
app.engine('ect', ectRenderer.render);
app.set('view engine', 'ect');
~~~

***
ここは、passportで認証を行ったときの情報をセッション内に保存し、
繰り返し使えるようにするための設定部分です。
session の secret は何でもいいです。

~~~js
app.use(session({
    secret: 'jaw key'
}));
app.use(passport.initialize());
app.use(passport.session());

~~~

***
ここは、passportを使ってJawboneのOAuth認証を行う部分です。

~~~js
var jawboneAuth = {
    authorizationURL: 'https://jawbone.com/auth/oauth2/auth',
    tokenURL: 'https://jawbone.com/auth/oauth2/token',
    callbackURL: 'http://localhost:8000/first'
};

passport.use('jawbone', new JawboneStrategy({
    clientID: process.env.JAWBONE_CLIENT_ID,
    clientSecret: process.env.JAWBONE_CLIENT_SECRET,
    authorizationURL: jawboneAuth.authorizationURL,
    tokenURL: jawboneAuth.tokenURL,
    callbackURL: jawboneAuth.callbackURL
}, function(token, refresh, profile, done){
    console.log("Get token");
    var user = {
        accessToken: token,
        refreshToken: refresh
    };
    var options = {
        access_token: token,
        client_id: jawboneAuth.clientID,
        client_secret: jawboneAuth.clientSecret
    };
    done(null, user);
}));

passport.serializeUser(function(data, done){
    done(null, data);
});

passport.deserializeUser(function(data, done){
    done(null, data);
});
~~~
OAuth認証をするためには以下の値が必要です。

- clientID
- clientSecret
- authorizationURL
- tokenURL
- callbackURL

上2つは .env に、下3つは jawboneAuth に記述しています。
基本的にはこれらの値を使って認証を行い、認証が通れば、accessTokenが取得できます。
このトークンを使うことで、 Jawbone API を使ってデータを取得することができます。
ここで取得したトークンはセッションに保存されるため、次からは認証をスキップして Jawbone API を利用できます。

***
ここでは、ブラウザからのアクセスに対して何を返すのかを記述しています。
ブラウザで``http://localhost:8000``にアクセスすると、一番上の ``app.get('/',function(...`` が反応します。
認証済ならJawboneから取得したデータを表示する``/data`` にリダイレクトさせ、未認証なら index.ect のページを提供します。

~~~js
var sessionCheck = function(req, res, next){
    if(req.isAuthenticated())
        return next();
        res.redirect('/login/jawbone');
};

app.get('/', function(req, res) {
    if(!req.isAuthenticated()){
        res.render('index', { title: 'Home' });
        console.log("Home");
    }
    else{
        res.redirect('/data');
    }
});
~~~
ブラウザに提供されたindex.ect 上のログインボタンを押すと``/login/jawbone``にアクセスし、scopeで指定したデータに対して、取得を許可するためのページがJawboneから提供されます。
今回scopeで指定しているのは、

- basic_read（ユーザの基本情報の呼び出し）
- move_read（歩行に関する情報の呼び出し）

他にscopeで指定できる内容については以下で確認できます。
[Jawbone Authenication](https://jawbone.com/up/developer/authentication)

~~~js
app.get('/login/jawbone',
    passport.authenticate('jawbone',{
        scope:['basic_read', 'move_read'],
        failureRedirect:'/'
    }), function(req,res){
});
~~~

Jawboneが提供するページの許可ボタンを押すと、アプリ管理画面の
``OAuth redirect URL``に設定した``http://localhost:8000/first``にリダイレクトされます。
ここで先程記述したpassportの認証が行われ、トークンがセッションに保存されます。

~~~js
app.get('/first',
    passport.authenticate('jawbone',{
        scope:['basic_read', 'move_read'],
        failureRedirect:'/'
    }), function(req, res){
        res.redirect('/data');
        console.log("logged in");
    }
);
~~~
認証が成功すると``/data``にリダイレクトされ、Jawboneのデータを取得する流れになります。
なお、Jawbone APIを利用するためには以下の値が必要です。

- access_token（セッションに保存されたトークンを呼び出す）
- client_id（.envの環境変数を呼び出す）
- client_secret（.envの環境変数を呼び出す）

これらを変数optionsに格納して、**jawbone-up** をrequireし、歩行のデータを呼び出します。この歩行データと一緒に userdata.ect をブラウザに提供します。
``up.moves.get({},function(...``では歩行のデータを取得できますが、それ以外のデータを取得する関数については以下を参考にしてください。
[jawbone-up](https://www.npmjs.com/package/jawbone-up)

~~~js
app.get('/data', sessionCheck, function(req, res){
    var options = {
        access_token: req.session.passport.user.accessToken,
        client_id: process.env.JAWBONE_CLIENT_ID,
        client_secret: process.env.JAWBONE_CLIENT_SECRET
    };
    var up = require('jawbone-up')(options);

    up.moves.get({}, function(err, data){
        if(err){
            console.log("Error receiving move data");
        }
        else{
            var jawboneData = JSON.parse(data).data;

            for(var i=0; i<jawboneData.items.length; i++){
                console.log(jawboneData.items[i].date + " : " + jawboneData.items[i].title);
            }
            var sendData = JSON.stringify(jawboneData);
            res.render('userdata', {title: 'User data', data: sendData});
        }
    });
});

app.get('/logout', function(req,res){
    req.logout();
    res.redirect('/');
});
~~~

# Webページ側
ここでは、ブラウザに提供するWebページについて説明します。
冒頭で views の中に中身のない以下のファイルを作成しました。

- layout.ect
- index.ect
- userdata.ect

.ectってどう書くの？ という方がいらっしゃると思いますが、**HTML**と同じで、ここでは+αな機能がついたものと考えて下さい。

layout.ectの下部に**<% content %>**といったhtmlでは見慣れない記述があります。
これがECTの機能の1つで、ここには index.ect 又は userdata.ect
の内容が入ります。
複数のページを提供する際に、それぞれ同じライブラリや見た目の設定を
利用することがあります。そういった共通部分を layout.ect に記述して共有することで、同じ記述をする手間を減らせます。
layout.ectの内容を他のectで呼び出す際は**<% extend 'layout.ect' %>**を先頭に付けます。

~~~layout.ect
<!DOCTYPE html>
<html lang="ja">
  <head>
  <meta charset="utf-8">
    <title><%= @title %></title>
    <meta name="viewport" content="width=device-width">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
  <style type="text/css">
    body{
        font-family: fantasy,sans-serif;
            margin: auto;
            text-align: center;
            max-width: 100%;
            height: 100%;
            width: auto;
            background-color: black;
            color: white;
            -webkit-text-size-adjust: 100%;
        -moz-text-size-adjust: 100%;
        -ms-text-size-adjust: 100%;
        -o-text-size-adjust: 100%;
        text-size-adjust: 100%;
    }
    hr{
        border-top: 4px double #FFFFFF;
    }
    #time{
        color: black;
    }
    .inputText{
        color: black;
    }
    #footer-nav{
        width: 100%;
    }

    #wakeupImg{
        width: 50%;
    }
  </style>
  </head>
<body>
<% content %>
</body>
</html>
~~~

***
index.ectは、Jawboneの認証ページに進むためのログインボタンを設けています。

~~~index.ect
<% extend 'layout.ect' %>
<h1>Jawbone Home</h1>
<form method="GET">
    <input type="submit" name="submit" formaction="/login/jawbone" class="btn btn-primary btn-lg btn-block" value="Jawbone Login">
</form>
<script>
</script>
~~~

***
userdata.ectでは、呼び出した歩行データを日付ごとにテーブルにして表示しています。
新たに**<%- ＠data %>**というECTの機能を利用します。
app.js内で userdata.ect を提供する部分がありますが、そこでページと一緒にdataに歩行データ(sendData)を格納して提供していました。

~~~app.js
res.render('userdata', {title: 'User data', data: sendData});
~~~
このdataをページ側で取得することができます。

~~~userdata.ect
<% extend 'layout.ect' %>
<body>
    <h1>User data</h1>
    <table id="moves" class="table table-condensed table-bordered">
    <tr>
    <th>Date</th>
    <th>Steps</th>
    </tr>
    </table>
    <a href="/logout" class="btn btn-primary btn-lg btn-block">Log out</a>
</body>
<script>
var jawboneData = <%- @data %>;
console.log(jawboneData);
for(var i=0; i<jawboneData.items.length; i++){
	var date = jawboneData.items[i].date;
	var title = jawboneData.items[i].title;
	console.log(date + "---" + title);
	$("#moves").append(
		$("<tr></tr>")
		.append($("<td></td>").text(date))
		.append($("<td></td>").text(title))
		);
};
</script>
~~~

**$**といった見慣れない記号がありますが、これは**jQuery**を利用する際の記号です。
**jQuery**はJavaScript用の便利なライブラリなので、詳しくは知りたい方は以下を参考にしてください。

[jQuery入門](http://qiita.com/Tocyuki/items/0b42c9225aafc995167a)
[jQuery日本語リファレンス](http://semooh.jp/jquery/)

以上で全ての環境が整いました。
ファイルエクスプローラーでapp.jsがあるフォルダに移動し、アドレスバーに``cmd``を入力してEnter。
以下のコマンドを実行することでサーバーが起動します。
ブラウザで``http://localhost:8000``にアクセスしましょう。

~~~コマンドプロンプト.
>node app
~~~

# おわりに
ここまで長々と書きましたが、Node.jsとexpressでアプリを作った経験のある人なら、OAuth認証がややこしいですが、説明文を読まないでもある程度理解できるかなと思います。
初めてこういったドキュメントを書いたので、わかりにくいかもしれませんが、フィットネストラッカーに興味がある人の足しになれば嬉しい限りです。
ありがとうございました。

(作成：神奈川工科大学 杉村研究室)

# 参考URL
[jawbone-up](https://www.npmjs.com/package/jawbone-up)
[passport](https://www.npmjs.com/package/passport)
[node.js テンプレートエンジンECTが爆速!!らしい件（ectの使い方)](http://www.webcyou.com/?p=5173)
[node.js+express-generator+passportでJawbone UPフィットネスデータをグラフ表示](http://qiita.com/daiki7nohe/items/415837ca9cf093a87bd7)