NEM-sdk を使ってサーバーレスな無記名掲示板を作る

## NEM-sdk を使ってサーバーレスな無記名掲示板を作ってみました

SNEMS（エスネムエス）プロトタイプ版
http://tinytintoy.com/snems/

アドレスを SNS に見立て、受信した XEM に付加されたメッセージを投稿内容とみなしてタイムライン状に表示するウェブアプリです。自前でデータベースは持たず、NEM の機能のみで実装してみました。SNS を目指したものの、できあがったのは今のところ無記名式の簡易掲示板です。

**（2017/12/28追記）**
意外にも好評をいただいたのでよりSNSっぽいサービスを目指したバージョンを公開しました。

SNEMS
http://www.snems.com

今のところスレッド式掲示板ですが徐々にSNSらしく整えていきますので末長く宜しく御願いします。

**投稿に際してのご注意**
----
- 投稿の削除はできません
- 暗号化をすると正しく表示されません

## 処理の流れ
- nem-sdk を読み込む
- connector オブジェクトを作る
- ハンドラを定義してから NIS に接続
- メッセージを hex から UTF8 に変換
- 取得したメッセージを好きなように整形して表示
- 最後にちょっと味付け

## NEM-sdk を読み込む
https://github.com/QuantumMechanics/NEM-sdk
のドキュメントの通り、ダウンロードした nem-sdk.js を読み込んで require します。

```html
<script src="nem-sdk.js"></script>
<script>
  // Include the library
  var nem = require("nem-sdk").default;
</script>
```

## connector オブジェクトを作る

WebSocket 接続のため connector オブジェクトを作ります。
参考：[NEM-sdkを使ってみる　WebSocket編](http://tadajam.hateblo.jp/entry/2017/06/12/073530) 

接続先は `nem.model.nodes.mainnet` という配列に入っているので、その中からランダムに選びます。

```javascript
//接続する supernode をランダムに選択
let getEndpoint = () => {
  let mainnet = nem.model.nodes.mainnet;

  // 62.75.171.41 と localhost を除いた node を取得する
  let target_node = mainnet[Math.floor(Math.random()* (mainnet.length - 2)) + 1];
  console.log(target_node);

  return target_node.uri;
}

let endpoint = nem.model.objects.create("endpoint")( getEndpoint(), nem.model.nodes.websocketPort);
let address = "NCKS2F-SWR7LF-XZWKD3-YWRWQO-VF5TBT-GC7M7E-LEKY";

let connector = nem.com.websockets.connector.create(endpoint, address);
```

（追記 2017.12.6）http://go.nem.ninja に接続しようとするとエラーになります。リトライするようにした方がいいですね。

## ハンドラを定義してから NIS に接続

```javascript
//直近のトランザクションを取得
let recent_transactions_handler = (res) => {
  console.log("recent_transactions_handler", res);
 };

//承認されたトランザクションを取得
let confirmed_transaction_handler = (res) => {
  console.log("confirmed_transaction_handler", res);
};

connector.connect().then(() => {
  console.log("Connected");

  nem.com.websockets.subscribe.account.transactions.recent(connector, recent_transactions_handler);

  nem.com.websockets.subscribe.account.transactions.confirmed(connector, confirmed_transaction_handler);
        
  nem.com.websockets.requests.account.transactions.recent(connector);
}, err => console.log("errorMessage", err) );
```

これでページ読み込み時に直近の投稿 25 件が取得できます。
また、トランザクションが承認された時にそのトランザクションを取得できます。

## メッセージを hex から UTF8 に変換
取得したトランザクションの transaction.message.payload は hex で返ってきているので nem.utils.format.hexToUtf8 を使用して UTF-8 に変換します。

```javascript
/* 例 payload が e381afe38198e38281e381bee38197e381a6efbc81 の場合*/

nem.utils.format.hexToUtf8( res.transaction.message.payload ); //=>はじめまして！
```

## 取得したメッセージを好きなように整形して表示

あとは取得したデータを好きなように整形して表示すればOKです。ここでは [riot](http://riotjs.com/ja/) を使っています。

コードの全体像は下記の通りです。

```html
<script src="nem-sdk.js"></script>
<script>
// Include the library
let nem = require("nem-sdk").default;
      
let posts = [];//取得した投稿内容を riot の tag に渡すための配列
riot.mount('message', {"posts":posts});
      
//接続する supernode をばらけさせる
let getEndpoint = () => {
  let mainnet = nem.model.nodes.mainnet;

  // 62.75.171.41 と localhost を除いた node を取得する
  let target_node = mainnet[Math.floor(Math.random()* (mainnet.length - 2)) + 1];
  console.log(target_node);

  return target_node.uri;
}

let address = "NCKS2F-SWR7LF-XZWKD3-YWRWQO-VF5TBT-GC7M7E-LEKY";
let endpoint = nem.model.objects.create("endpoint")(getEndpoint(), nem.model.nodes.websocketPort);
      
let connector = nem.com.websockets.connector.create(endpoint, address);

let recent_transactions_handler = (res) => {
  console.log("recent_transactions_handler", res);
  res.data.map(( d )=>{
    if(d.transaction.message.payload){
      posts.push({
        timeStamp:(new Date(d.transaction.timeStamp * 1000 + Date.UTC(2015, 2, 29, 0, 6, 25, 0))).toString(),
        amount:d.transaction.amount,
        message:nem.utils.format.hexToUtf8( d.transaction.message.payload )
      });
    }
  });
  riot.update('message', {"posts":posts});
};

let confirmed_transaction_handler = (res) => {
  console.log("confirmed_transaction_handler", res);
  if(res.transaction.message.payload){
    posts.unshift({
      timeStamp:(new Date(res.transaction.timeStamp * 1000 + Date.UTC(2015, 2, 29, 0, 6, 25, 0))).toString(),
      amount:res.transaction.amount,
      message:nem.utils.format.hexToUtf8( res.transaction.message.payload )
    });
  }
  riot.update('message', {"posts":posts});
};

connector.connect().then(() => {
  console.log("Connected");

  nem.com.websockets.subscribe.account.transactions.recent(connector, recent_transactions_handler);
  nem.com.websockets.subscribe.account.transactions.confirmed(connector, confirmed_transaction_handler);

  nem.com.websockets.requests.account.transactions.recent(connector);
}, err => {
  riot.mount('error', {"message":err});
});
</script>
```

## 最後にちょっと味付け

10XEM にメッセージを添えて投稿した人と 0.1XEM で投稿した人の扱いが同じなんて、なんだかおかしいですよね。**そこで XEM の量に応じて文字が大きくなるようにしてみました。**お金持ちほど発言力が増すなんて、ちょこっと世知辛いですね。

## まとめ
基本的な機能を使うだけで簡易的な掲示板ができあがりました。アドレスを何に見立てるか、そして XEM やメッセージをどう使うかを工夫するだけでも無限の可能性が広がるのではないかと思います。今回作った SNEMS も、せっかくなので立派な SNS に育てあげたいです。