# 실시간 데이터 주고받기

> 채널(room.plugin)로 같은 채팅방 사람들끼리 채팅 메시지가 아닌 데이터를 주고받고, 받은 데이터를 검증하는 흐름을 설명해요.

채널을 쓰면 같은 채팅방에 있는 사람들끼리 채팅 메시지가 아닌 데이터를 실시간으로 주고받을 수 있어요. 함께 보기, 게임, 투표처럼 모두의 화면을 맞춰야 하는 기능에 써요. 함수 목록과 한도의 정본은 [채널 API와 한도](/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]);  // 그 닉네임 한 사람에게만
```

- 배열 원소는 **문자열·숫자·불리언**만 돼요. 객체·배열·`null`은 전송 중에 빠지므로, 구조가 필요하면 원소를 나눠 보내거나 문자열로 만들어 보내세요.
- `send`로 보낸 데이터는 보낸 사람 자신도 받아요. `sendTo`는 받는 사람만 받아요. 숨김 채팅금지된 사람이 보내면 둘 다 본인에게만 되돌아가요.
- 무료 채팅방은 최근 60초 동안 받는 사람 수 합계에 한도가 있어, 넘으면 오류 428이 나요. 자주 보내는 기능은 보내는 횟수를 줄이세요. → [채널 API와 한도](/doc/3.5)

## 받기 {#receive}

```js
dice.onReceived(function (room, msg) {
	// msg.user  보낸 사람 { nick, id, level, auth, ... }
	// msg.type  0 모두에게 보낸 것, 1 나에게만 보낸 것
	// msg.data  보낸 배열
});
```

콜백은 다른 이벤트처럼 `(room, data)`를 받아요. 같은 데이터는 [이벤트](/doc/3.3)의 `before.plugin`, `after.plugin`으로도 와요.

## 받은 데이터 검증하기 {#validate}

채널 데이터는 채팅방에 있는 누구나 개발자 도구로 원하는 값을 넣어 보낼 수 있어요. 받은 쪽에서는 항상 검증해요.

- 원소 개수와 타입, 값의 범위를 확인하고 맞지 않으면 버려요.
- 화면에 넣을 때는 `textContent`를 쓰거나 이스케이프해요. `room.print`는 HTML로 해석해요.
- 보낸 사람을 확인할 때는 `msg.user.auth`(0 손님, 1 회원, 2 부관리자, 3 관리자, 4 서버)를 봐요. 닉네임만으로 판단하지 마세요.
- 결과의 공정함이 중요하면(주사위, 추첨) 한 사람이 정한 값을 그대로 믿지 말고, 서버가 정해 서버 API로 보내게 하세요. → [서버에서 채팅방으로 알림 보내기](/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와 한도](/doc/3.5)

```js
// 관리자가 고른 영상 아이디를 저장하고 지금 있는 사람에게도 보냄
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>
```

함께 보기와 게임처럼 더 큰 예제는 [유튜브 함께 보기](/doc/4.1)와 [가위바위보](/doc/4.2)에 있어요.
