# リアルタイムデータの送受信

> チャンネル（room.plugin）を使って同じチャットルームの参加者どうしでチャットメッセージ以外のデータをやり取りし、受け取ったデータを検証する流れを説明します。

チャンネルを使うと、同じチャットルームにいる人どうしで、チャットメッセージ以外のデータをリアルタイムにやり取りできます。一緒に見る機能、ゲーム、投票のように、全員の画面をそろえる必要がある機能に使います。関数一覧と上限の正式な情報は[チャンネルAPIと上限](/ja/doc/3.5)にあります。

## チャンネルを開く {#open}

`room.plugin.add(チャンネル名)` は、その名前のチャンネルオブジェクトを返します。同じ名前でもう一度呼ぶと、同じオブジェクトを返します。同じチャンネル名を使う人どうしでデータをやり取りします。

```js
U.chat('my-room').on('after.create', function (room) {
	var dice = room.plugin.add('dice');
});
```

チャンネル名は英数字で始め、英数字・`_`・`-` だけを使った64文字以内にしてください（このルールを守らないと `save` は拒否されます）。ほかの機能と重ならないよう、自分のサービス名を先頭に付けることをおすすめします。

## 送信する {#send}

データは常に**配列**で送ります。

```js
dice.send([4]);                       // チャットルームの全員に（自分を含む）
dice.sendTo('홍길동', ['hello', 1]);  // そのニックネームの1人だけに
```

- 配列の要素に使えるのは**文字列・数値・真偽値**だけです。オブジェクト・配列・`null` は送信中に取り除かれるため、構造が必要な場合は要素を分けて送るか、文字列にして送ってください。
- `send` で送ったデータは送信者自身も受け取ります。`sendTo` は受信者だけが受け取ります。隠しミュート中の人が送った場合は、どちらも本人にだけ戻ります。
- 無料チャットルームでは、直近60秒間の受信者数の合計に上限があり、超えるとエラー428になります。頻繁に送る機能では送信回数を減らしてください。→ [チャンネルAPIと上限](/ja/doc/3.5)

## 受信する {#receive}

```js
dice.onReceived(function (room, msg) {
	// msg.user  送信者 { nick, id, level, auth, ... }
	// msg.type  0 全員に送られたもの、1 自分だけに送られたもの
	// msg.data  送られた配列
});
```

コールバックはほかのイベントと同じく `(room, data)` を受け取ります。同じデータは[イベント](/ja/doc/3.3)の `before.plugin`、`after.plugin` でも届きます。

## 受け取ったデータを検証する {#validate}

チャンネルのデータは、チャットルームにいる誰でも開発者ツールで好きな値を入れて送れます。受信側では必ず検証してください。

- 要素の数と型、値の範囲を確認し、合わないものは捨てます。
- 画面に表示するときは `textContent` を使うか、エスケープします。`room.print` はHTMLとして解釈します。
- 送信者を確認するときは `msg.user.auth`（0ゲスト、1会員、2モデレーター、3管理者、4サーバー）を見ます。ニックネームだけで判断しないでください。
- 結果の公平さが重要な場合（サイコロ、抽選）は、1人が決めた値をそのまま信用せず、サーバーで決めてサーバーAPIから送るようにしてください。→ [サーバーからチャットルームに通知を送る](/ja/doc/2.5)

## 入力コマンドを受け取る {#parser}

`addParser` を登録すると、基本スキンの入力欄に `!チャンネル名 引数...` と入力したときに、その関数が引数の配列を受け取ります。その入力はチャットメッセージとして送信されません。関数が `false` を返すと処理しなかったものとみなされ、入力は通常のメッセージとして送信されます。

```js
dice.addParser(function (args) {
	// 入力: !dice  →  args: []
	dice.send([Math.floor(Math.random() * 6) + 1]);
});
```

## 状態を保存する {#save}

`save(配列)` は、チャンネルの最新の状態をサーバーに保存します。あとから入室した人は、入室時に保存された値を受け取ります。`clear()` は保存した値を削除します。

- 保存できるのはモデレーター以上だけです。
- 保存しても、いまいる人には送信されません。いまいる人にも知らせるには `send` も一緒に呼んでください。
- 入室時に受け取る保存値は `type` 1（自分だけ）で、送信者はサーバーユーザー（`auth` 4、ニックネームなし）として届きます。誰が保存したかはわかりません。
- 保存サイズには上限があり、無料チャットルームでは別のチャンネルに保存すると、既存のチャンネルの保存値が削除されます。→ [チャンネルAPIと上限](/ja/doc/3.5)

```js
// 管理者が選んだ動画IDを保存し、いまいる人にも送る
video.save(['dQw4w9WgXcQ']);
video.send(['dQw4w9WgXcQ']);
```

## 閉じる {#destroy}

`destroy()` はチャンネルを閉じ、登録した受信関数とコマンド関数もあわせて破棄します。同じ名前でもう一度 `add` すると、新しいチャンネルが開きます。

## コード全体 {#full-code}

`!dice` と入力するとサイコロを振り、結果を全員に知らせる例です。

```html
<script>U=window.U=window.U||{},U.events=U.events||[],U.chat=function(n){return{on:function(e,t){U.events.push([n,e,t])},off:function(e){for(var t=U.events.length;t>0;)U.events[--t][0]==n&&U.events[t][1]==e&&U.events.splice(t,1)}}};</script>
<script async src="https://client.uchat.io/uchat.js"></script>
<u-chat room="my-room" style="display:inline-block; width:100%; max-width:500px; height:500px;"></u-chat>
<script>
function escapeHtml(text) {
	return String(text).replace(/[&<>"']/g, function (c) {
		return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
	});
}

U.chat('my-room').on('after.create', function (room) {
	var dice = room.plugin.add('dice');

	dice.onReceived(function (room, msg) {
		var value = msg.data[0];
		if (msg.data.length !== 1 || typeof value !== 'number') return;
		if (value % 1 !== 0 || value < 1 || value > 6) return;
		var nick = msg.user ? msg.user.nick : '?';
		room.print(escapeHtml(nick) + 'さんのサイコロ: ' + value);
	});

	dice.addParser(function () {
		dice.send([Math.floor(Math.random() * 6) + 1]);
	});
});
</script>
```

一緒に見る機能やゲームのような、より大きな例は[YouTubeを一緒に見る](/ja/doc/4.1)と[じゃんけん](/ja/doc/4.2)にあります。
