Node.jsとChrome拡張機能を使ってリアルタイムで通知を出す


Node.jsとChromeの拡張機能を組み合わせることでサイト上で何かが起こった時にリアルタイムに通知を出す事ができるようになります。

例えば・・・

* サイトで何かしらの申し込みがあった時にチームメンバーにお知らせをしたい
* サイトでデイリーの目標CV数に到達したときにお知らせをしたい

などなど、、、

とりあえず今回はGitLabを使ってマージしたときにポップアップで通知がでるようなものを作成したいと思います。毎回ポップアップがでるのは鬱陶しい！という方はアイコンのバッチを変えたりとかでもいいと思います。

本当は拡張機能を公開とかできればいいんですが、お金かかるし、そこまで作り込んでいないので、断念。


# 超簡単な図
![スクリーンショット 2014-11-25 22.58.45.png](https://qiita-image-store.s3.amazonaws.com/0/6808/a5c9c972-2c2a-3306-7be0-d2ec20401f62.png "スクリーンショット 2014-11-25 22.58.45.png")



# リクエストを受け付けるNode.jsサーバを用意

リクエストを受け付けるNode.jsサーバを用意します。
今回は

* node.js v0.10.26
* express 4.9.0

です。 

環境構築方法は下記のQiitaとか参考にしてみてください。
注意点としてはexpressのバージョンが3系と4系で大分違うので、
Googleで検索してヒットした記事通りにやってもうごかん！っていうことになります。

Node.jsのインストール
http://qiita.com/takashibagura/items/5b67126072c159bf0f9e

http://qiita.com/uemura/items/a9183ffcec2b185c4c6e


Expressのインストール
http://qiita.com/pakiln/items/826a9199697576e2e24a


Express 4系についてのまとめ
http://qiita.com/zaru/items/68b4f64c1f0d10b6a27e



## サーバサイドのソースコード

なんか必要ないところいっぱいあると思いますが、あまり気にせず・・・

全部app.jsにぶち込んでます。

```app.js
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var http = require('http');
var app = express();

// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));


//===========================
//ルーティング
app.post('/', function(req, res) {
	var commits = req.param('commits');
	var commit_message = "";
	for(var i=0; i<commits.length; i++) {
		//マージのcommitメッセージはなくす
		if(commits[i]['message'].indexOf('Merge branch') < 0 ) {
			console.log(commits[i]['message']);
			commit_message += commits[i]['message'];
		}
	}
	io.sockets.emit('push', {value: commit_message});
	res.render('index', { title: 'Express' });
});
//===========================


// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.set('port', process.env.PORT || 3000);

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});


//Websokcet接続の部分
var server = http.createServer(app);
server.listen(app.get('port'), function() {
	console.log("Express server listening on port " + app.get('port'));
});
var io = require('socket.io').listen(server);
io.sockets.on('connection', function(socket) {
	console.log('connection start');
});

module.exports = app;
```

なんだかごちゃごちゃありますが、ポイントとしては3つです。

## 1. bodyParserを使ったPOSTデータの受け取り

POSTされたパラメータをNode.js側で受け取るにはbodyParserというミドルウェアを使う必要があります。
その辺を下記で行ってます。

```javascript
var bodyParser = require('body-parser');

・・・・・

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

```

## 2. ルーティングの設定

今回の場合はpostなので`app.postでデータを受け取ります。

commitメッセージをchrome拡張機能のポップアップに表示させるためにWebhookでPOSTされたデータの中からcommitメッセージを取得してます。

hookとして叩いたURLから何か画面を表示させる訳ではないので、viewの指定とかいらなそうなんですが、ちゃんと指定しないとうまく動かなかったので、`res.render`も指定してます。

io.sockets.emitで指定したキーワード（ここではpush）とchrome拡張機能側で受け取りを指定するキーワードと一緒にする必要があります。


```javascript
app.post('/', function(req, res) {
	var commits = req.param('commits');
	var commit_message = "";
	for(var i=0; i<commits.length; i++) {
		//マージのcommitメッセージはなくす
		if(commits[i]['message'].indexOf('Merge branch') < 0 ) {
			console.log(commits[i]['message']);
			commit_message += commits[i]['message'];
		}
	}
	io.sockets.emit('push', {value: commit_message});
	res.render('index', { title: 'Express' });
});

```

## 3. Websocketの通信

Socket.ioをインストール

```
npm install socket.io
```

Websokcetのコネクション開始部分

```javascript
var server = http.createServer(app);
server.listen(app.get('port'), function() {
	console.log("Express server listening on port " + app.get('port'));
});
var io = require('socket.io').listen(server);
io.sockets.on('connection', function(socket) {
	console.log('connection start');
});
```

# Chrome拡張機能の用意

まずは設定ファイルとなるmanifest.jsonです。

socket.io.jsはこちらにあります。
https://github.com/automattic/socket.io-client


```manifest.json
{
  "manifest_version": 2,
  "name": "拡張機能の名前",
  "version": "1.0",
  "description": "説明文",
  "browser_action": {
        "name": "拡張機能の名前"
  },
  "permissions": [
    "notifications",
  ],
  "background": {
    "persistent": false,
    "scripts": ["socket.io.js", "eventpage.js"]
  },
  "icons" : {
    "16":"16x16のアイコン画像",
    "48":"48x48のアイコン画像"
  }
}
```


こちらが拡張機能のメインとなるjsです。
といってもたいしたことしてませんが、、、

先ほどのmanifest.jsonでbrowser_actionでdefault_popup（ツールバーのアイコンをクリックした時にポップアップを出すようにする）を指定するとなぜか`chrome.browserAction.onClicked.addListener`がイベントとれなくなるので注意。


```eventpage.js
var socket = io.connect("websocketサーバーのURL");

//websocketで接続した時
socket.on('push', function(message) {
        var opt = {
                type: 'basic',
                title: 'タイトル',
                message: message.value,
                iconUrl: "ポップアップで表示させるアイコン"
        }
        //アイコンにつけるバッチ
        chrome.browserAction.setBadgeText({text:"1"});
        //ポップアップ通知
        chrome.notifications.create("", opt, function(id){});
});

//ツールバーをクリック時にバッチを消す
chrome.browserAction.onClicked.addListener(
        function(tab) {
                chrome.browserAction.setBadgeText({text:""});
        }
);

```


# イベント発生時のWebhookを設定をする
何かしらの通知をしたいイベントが発生したときにNode.jsサーバにリクエストを飛ばします。

今回はGitLabのWebhooksでPush eventsが発生したときにNode.jsサーバにリクエストを飛ばすようにします。

この部分を応用してイベント発生時にNode.jsのサーバにhttpリクエストを飛ばせば、リアルタイムで通知ができるようになるはずです！！