1

React+Fluxxor+socket.ioでfluxなチャットを作った

fluxxorとsocket.ioを使ってチャットを作った。大変ミニマムな感じの実装を心がけてて、今後何かWebアプリを作る時はこれを少し組み替えて作るぞというのを意識してる。ようするに低機能最低限でしょぼい。サーバー側はexpressとmongodb。

https://github.com/shokai/node-flux-boilerplate

herokuでも動く
https://node-flux-boilerplate.herokuapp.com/

クライアントはこんな実装


Fluxアーキテクチャ

Reactはなんとなくわかってfluxをやるか、と思ったらflux npmの中がdispatcherしかなかった。
flux.DispatcherのwaitForの実装


まずこのへんを読んでフムフムなるほど(わかったようなわからないような)となる。
Flux | Application Architecture for Building User Interfaces


で、このスライド見たらわかりやすすぎて気絶した。とりあえずなんらかのfluxフレームワーク使ってチャットでも作る事にした。


あとこっちも、フレームワークなしで最低限のfluxを自作するとしたらこんな感じになるのね、とわかって気が楽になる。
これ読んでおけば、Fluxxor等のフレームワークがどういう工夫をしているか差分を理解しやすくなりそう。
10分で実装するFlux



Fluxxorというフレームワークを使う



http://fluxxor.com/


FluxxorはFluxなフレームワークで、StoreとActionとDispatcherを担当する。ReactをViewとして使うこと前提に作られている。

この記事がとてもわかりやすい
Fluxなフレームワーク、Fluxxorの紹介 – マルシテイアは月の上


で、Fluxxor+ReactにSocket.ioを追加してチャットをつくってみて、こんな感じの実装になった。


fluxはaction→dispatcher→store→view→actionの4つの部品で構成されるが、fluxxorは三叉のロゴのとおりDispatcherが無くてaction/store/viewだけになる。

Dispatcherはfluxxorを使うことでactionとstoreに内蔵されるようになっている。そしてactionからはdispatch関数のみが、storeからはbindActionでの受信のみがアクセスできる様になっているので、action→storeの1方向にしかメッセージが流れない。試してないけどwaitForで順序制御もできるようだ。

fluxorのstoreにはeventemitterが継承されていて、emit(“change”)するとViewにイベントが飛ぶ。ViewはcreateClassした時のmixinsにFluxxor.storeWatchMixin(Store名)を指定すると、changeイベントを受けたらrenderを呼ぶようになっている。ここでも1方向のメッセージングが強制される。

Viewからactionを呼び出すのはthis.getFlux().actionsの下に関数が生えているので、それを呼び出す。

ふつうにfluxを自分で作ったら、dispatcherがどこからでも見える場所にあったりとかで、1方向にしかメッセージを流さないことを心がける事はできても、メッセージの逆流を防ぐ実装をするのはちょっと面倒臭い。fluxxorに乗ってやると円環の理が強制的に生まれる。


socket.ioどこに置くか問題


socket.ioからの入出力はactionを通してfluxとやりとりする。

受信
socket.ioからチャットメッセージ受信→action→storeのlogsに追加→reactがView更新

送信
チャット送信ボタン押す→action→socket.ioでサーバーに送信

socket.io関係のコードをどこに置くかでけっこう迷った。
最初はChatStoreに持たせようとして、でもただの「接続しました!」みたいなStore関係ないメッセージ表示するようなコードはどこに置くのか。

結局fluxの外に独立した部品として設置してactionで通信することにしたけど、送信時のactionからのdispatchを受信させる方法が良くない。dispatchはstoreのbindAction以外にも、flux.on “dispatch”, (type, payload) ->でも受信できるけど、

An action of type send-chat was dispatched, but no store handled it dispatcher.js:105


というwarningがどうしても出される。これを避けるにはどうしてもStoreのフリをするしかないし、そもそもdispatchがstoreじゃない所に飛ぶ可能性を作るのはせっかくfluxとかいう円環の理を作ってバグの場所を特定しやすくした意味がぶち壊しになるのでやめた。
おとなしくactionから関数を呼び出すことにした。socktes/という別ディレクトリを掘ってその中にsocket.io関連のコードはまとめて、fluxとのやりとりは必ずaction経由でやる。

0

cloudBitのHTTP APIをNode.jsで使う

littleBitsにはcloudBitという組み込みLinuxが入っててWiFiを積んでるブロックがある。最近日本でも発売された。

Amazon.co.jp | littleBits Cloud Starter Bundle | ホビー 通販


回路

こんな感じで左から
電源→sound trigger→dimmer→cloudBit→LEDやサーボ
のように接続すると、音に反応してcloudBitがwebhookを送ってくれる。また、こっちからHTTP POSTするとcloudBitから出力できて、LEDやサーボを動かしたりもできる。ようするにwebから使えるアナログ入出力ブロックだ。

sound triggerとcloudBitの間にdimmerを挟むと音の反応しきい値が調整しやすい。



tokenとdevice_id取得

littleBits Cloud Controlで新規プロジェクト登録して、tokenとdevice_idを取得する。

export LB_TOKEN=a1b2cdefghjiklasdf
export LB_DEVICE=98d76reb


出力


POSTするとcloudBitから電圧を0〜100で指定して出力できる。一定時間経つと出力は0に戻る。
request = require 'request'

little_out = (percent, duration, callback = ->) ->
request
method: 'POST'
url: "https://api-http.littlebitscloud.cc/devices/#{process.env.LB_DEVICE}/output"
headers:
Authorization: "Bearer #{process.env.LB_TOKEN}"
Accept: 'application/vnd.littlebits.v2+json'
postData:
percent: percent
duration_ms: duration
, callback

# 出力90で3000ミリ秒
little_out 90, 3000, (err, res, body) ->
console.error err
console.log body

littlebits-cloud-httpというnpmがあるけど勝手に標準出力になんか出してきたり、APIがなんか変だし実装も妙にまわりくどかったのでrequestでやった。


webhook設定


curl -i -XPOST \
-H "Authorization: Bearer $LB_TOKEN" \
-H "Accept: application/vnd.littlebits.v2+json" \
-d subscriber_id=http://example.com/littlebits \
-d publisher_id=$LB_DEVICE \
-d publisher_events=amplitude:delta:ignite \
https://api-http.littlebitscloud.cc/subscriptions
http://example.com/littlebitsにwebhookしてくる。subscriber_idに他のcloudBitのdevice_idを指定すると直接通信させる事もできるらしい。

amplitude:delta:igniteは電圧が上がった時に反応する。
BitCloud API Documentation

amplitude –––– when there is any voltage (catch-all, default)
amplitude:delta:sustain –––– when high voltage is constant (eg button being held)
amplitude:delta:ignite –––– when there is significant voltage jump (eg button press)
amplitude:delta:release –––– when there is significant voltage drop (eg button release)
amplitude:delta:nap –––– when low voltage is constant (eg idle bitSnap system)
amplitude:level:active –––– generic, when there is high voltage (eg during a sustain or maybe just ignited)
amplitude:level:idle –––– generic, when there is low voltage (eg during a long nap or maybe just released)



webhook受信


express = require 'express'
bodyParser = require 'body-parser'

router = express()
router.use bodyParser.json()

http = require('http').Server(router)

router.post '/littlebits', (req, res) ->
res.end 'ok'
console.log req.body
percent = req.body.payload.percent
console.log "percent: #{percent}"

http.listen 3000

1秒もかからずwebhookが来る。POSTのbodyはjsonで、こんな感じ
{ type: 'amplitude',
timestamp: 1429543559474,
user_id: 58158,
bit_id: '98d76reb',
payload: { percent: 85, delta: 'ignite' } }


hubotで受信して、部屋のわいわい感をチャットに流すとかできる。


webhook解除


DELETEで解除する
curl -i -XDELETE \
-H "Authorization: Bearer $LB_TOKEN" \
-H "Accept: application/vnd.littlebits.v2+json" \
-d subscriber_id=http://example.com/littlebits \
-d publisher_id=$LB_DEVICE \
https://api-http.littlebitscloud.cc/subscriptions


全デバイスの設定確認
curl -i -XGET \
-H "Authorization: Bearer $LB_TOKEN" \
-H "Accept: application/vnd.littlebits.v2+json" \
https://api-http.littlebitscloud.cc/devices

0

philips-hue npm作った

Node.jsとMIDIコントローラでHueを調光するの中で使っていたhueライブラリを切り出してnpmにした。

https://www.npmjs.com/package/philips-hue
https://github.com/shokai/node-philips-hue

なんで作ったかちゃんと思い出せないんだけど、たしかRubyのhue gemについているコマンドが引数多くて使いにくかったり、他のコマンドだと全ランプにまとめてeffect:colorloopを設定するのが面倒だったり、Nodeのライブラリだと色々あるけどAPIの抽象化方法に違和感あったりで満足するのが無かったからだったと思う。


インストール

% npm install philips-hue -g


コマンド

philips-hueというコマンドが付いている。


–lightで番号を指定しなければ全てのランプに指令を送る。


ライブラリ


ふつうにhue.getBridge()やhue.auth()で認証プロセスを組み立てるのもできるけど、アプリ作る時に認証情報を保存して次回起動時に読み出すのが面倒だったので、loadConfigFileという設定ファイルを読み出す(ファイル無ければ認証して作る)便利関数を用意しておいた。
var Hue = require('philips-hue');
var hue = new Hue();

var conf_file = process.env.HOME+'/.philips-hue.json'; // 設定ファイル
hue.loadConfigFile(conf_file, function(err, conf){
var light = hue.light(1);
light.on(); // 点灯

var state = {bri: 200, sat: 120, hue: 50000};
light.setState(state, function(err, res){
if(err) return console.log(err);
console.log(res);
});

hue.light(2).off(function(err, res){ // 消灯
if(err) return console.error(err);
console.log(res);
});
});


ご意見ご感想


バグや質問はtwittergithub issueへどうぞ

0

Node.jsとMIDIコントローラでHueを調光する

できた

MIDIコントローラ、安いしツマミとかスライダーがいっぱい付いてて便利だと思う。

ツマミで色相(hue)、スライダーでbrightness(輝度)を調整できる。
動画では使ってないけど横のボタンで彩度(saturation)を0にして真っ白にもできる。



ソースコード

これをgit cloneしてnpm installしてnpm startすれば起動する。
https://github.com/shokai/hue-korg-control

MIDIコントローラの制御にはkorg-nano npmを使っている。KORG nanoKontrolを操作すると”knob:1″とか”slider:3″とか”button:play”というイベントを発火させてくれるのでEventEmitterで受けるだけでいい。

Hueの操作は、nodeのhueライブラリにどうもしっくり来るものが無かったので1から自作した。HueのAPIは単純なのに、どれもなんだか過剰にwrapしすぎているように見える。
Web+DB Press vol.83でHueのAPIについては解説してて、それを見たらすぐ実装できた。


MIDIコントローラ

家にたまたまあったKORG nanoKontrolで操作している。nanoKontrolは絶版になってて中古で1万円ぐらいなんだけど、後継機のnanoKontrol2が3〜4千円ぐらいで売ってる。たぶんそっちでも同じように使える。

USB接続で、ドライバなしでMacに接続していきなり使えて便利だった。なんか便利っぽいのでもう1,2個ぐらいほしい。



NodeでMIDI

MIDIデバイスはUSBで接続できるやつも最近はたくさんあるみたいだし、ツマミ・スライダー・ボタンがたくさん付いているのでコントローラとして優秀だと思う。

Node.jsの場合はmidi npmでMIDIデバイスとやりとりできる。midi npmはRtMidiというMac/Windows/Linux対応してるC++ライブラリのラッパーなので、Raspberry Piとかでも動かせそう。

midiは大変簡単なバイナリプロトコルで、1byte(8bit)の1番上の1bitが1ならコマンド、0ならデータで、のこり7byteにコマンドやデータ内容が書かれている。
今回はkorg-nano npmを使っているが、midiデバイスをNodeで扱うとしたらこんな感じでできる。

EventEmitter = require('events').EventEmitter

midi = require 'midi'
debug = require('debug')('midi-device')

module.exports = class Device extends EventEmitter

constructor: (device_name=".*") ->
@input = new midi.input
for i in [0...@input.getPortCount()]
name = @input.getPortName i
debug "found device [#{i}] \"#{name}\""
if new RegExp(device_name, 'i').test name
debug "openPort #{i}"
@input.openPort i
break

## midi messageを受信
@input.on 'message', (delta, raw) =>
@emit 'message', {delta: delta, raw: raw}

device = new Device("keyboard")

device.on 'message', (data) ->
console.log data.raw


Rubyではmidi gemというのがあって、試してないけどgithubを見た感じよくメンテナンスされているように見えるので、これでもいい気がする。

0

テストがあるとプルリクしやすい

印象としてそんな感じがする。

だいたいプルリクする時って、バグを見つけて直すのはすぐできるんだけど、自分の修正が何か別のところで副作用を起こしているかもしれない、という不安があってグダグダ色々検証したり他の部分のコードも一応読んだりする時間の方が長いと思う。
そういう時にとりあえずテスト通っていれば、テスト書いたリポジトリ主がokしているような気になってくるから、すぐプルリク出せる。

なるべくrake testとかnpm testとかコマンド一発実行すればテストが走るようにしておいてくれるとプルリクしやすくてうれしい。

プルリク受け入れる側も、Travis CIとかでプルリク自体にテスト通過しましたバッジがついていると安心感がある。


テストの書き方色々ある

去年からnode.js使い始めて、色々作ってるんだけどテストの書き方色々ある気がするのでメモしておく。
具体的な書き方は各リポジトリの中見ればわかる。


テストが書きにくいものは内部ライブラリのテスト書く

UIとか画像処理のテストとか書きにくい。
あとhubot scriptのテストの書き方がよくわからない。robot.respondとか、ユーザーの入力にフックしてるようなのの書き方がドキュメントどこ探しても見つからなかった。

hubot-rss-readerの場合、rss-checkerという内部ライブラリをhubot scriptと別ファイルに作っておいて、mochaでテスト書いた。hubot scriptの方はテスト書いてない。
それをgrunt-simplemochaで実行できるようにして、さらにgrunt testをnpm testから実行できるようにしている。package.jsonのscriptsにコマンドが書けるので便利。

{
"name": "hubot-rss-reader",
"private": false,
"version": "0.6.5",
"description": "Hubot RSS Reader",
"main": "scripts/hubot-rss-reader.coffee",
"scripts": {
"test": "grunt test" (注:これがnpm testで実行できる)
},
(略)
.travis.ymlを置いて、GitHubからTravis CIへのフックを設定すればgit pushしたりプルリク受けたりするだけでTravis CI上でnpm testが走るようになる。


アカウント情報を暗号化してTravis CIに保存できる

gyazo-api npmのテストでは、毎回gyazoに画像をアップロードしてリスト取得して削除している。
oauthのaccess tokenが必要なので、travis encryptしてtokenを保存している。

% travis encrypt GYAZO_TOKEN=a1b2cde --add env
これを実行すると.travis.ymlに環境変数を暗号化して保存できる。

language: node_js
node_js:
- '0.10'
env:
secure: asdfadajowejfoaiwejfoawiejfpaiup(暗号化されたtoken)

Travis CI: Environment Variables


ローカルで実行する時は普通に環境変数でtokenを渡せばtest実行できる。
% GYAZO_TOKEN=a1b2cdef npm test


サーバーでtest実行できない時は、lintだけでもかけておく

というかlintは設定するコスト低いので全てのプロジェクトでかけるようにはしている。

testできないものの例としては、node-ble-firmataはあらかじめ特別なファームウェアを書き込んだBLE付きArduinoを用意する必要があるので、これはどうやってもTravis CIでテスト実行できない。
しょうがないので、testは手元でやるとして、CIではcoffee-lintだけかけておく。coffeeはわりと文法間違える人いるのでlintだけでもあるとなんか安心感が違う。

普通にnpm installするとbluetooth関係のnpmをインストールしようとしてCライブラリが無くてTravis CIではビルド失敗するので、devDependenciesだけワンライナーでインストールするようにした。

language: node_js
node_js:
- '0.10'
install: node -e 'console.log(Object.keys(require("./package.json").devDependencies).join(" "))' | xargs npm install
script: npm run lint
これでnpm run lintだけが実行される。

ちなみにnpm install –devしたらdevDependenciesだけインストールできそうだったんだけど、依存解決が無限ループして20分ぐらいインストールし続けて終了するのでやめた。


複数の言語でテストする

socket.io-client-simple gemなんかがそうで、nodeで立てたsocket.ioサーバーにrubyで接続して通信できるかテストをしている。

.travis.ymlでメイン言語をrubyにして、それとは別にnodeのバージョン指定をするとrubyもnodeもインストールされる。npm installはbefore_scriptのタイミングでやると、gemのインストール→npmのインストール→rake test実行 という順番になる。
language: ruby
node_js:
- '0.10'
rvm:
- '2.1.2'
before_script:
- npm install


slackに通知する


Travis CI: Configuring Build Notificationsに書いてあるが、slackでtravis integrationを追加してtokenを取得してから、

% travis encrypt "[account]:[token]" --add notifications.slack

で.travis.ymlに通知設定が保存される。CI終わったらslackに通知が来る。


その他

あとは、READMEにサンプルコードの実行方法を書いておいたり、基本的な機能がすぐ試せる実行コマンドを付けておくとissueとかのトラブルシュートに役立つ。なんかきかれたら「〜〜を実行できる?」とか質問しやすい。

coffee-lintで1行の文字数が最大80文字という設定がデフォルトになってるけど、80文字というのは昔のパンチカードの文字数が80文字だったからMacとかのターミナルの横幅もデフォルト80文字になって未だに引きずっている意味のない数値らしいので、80文字にこだわる必要はない。
むしろ長いURL文字列を作る時とかに書きにくくなる。coffee-lintでは0を指定すると制限解除できる。

    coffeelint:
options:
max_line_length:
value: 0