Uma simples amostra de RTCDataChannel
=====================================

A interface `RTCDataChannel` é um recurso da WebRTC API que permite abrir um canal entre dois pares sobre os quais você pode enviar e receber dados arbitrários. A API é intencionalmente semelhante à WebSocket API (en-US), de modo que o mesmo modelo de programação pode ser usado para ambos.

Neste exemplo, abriremos um `RTCDataChannel` para realizar a conexão entre dois elementos na mesma página. Embora seja obviamente um cenário artificial, é útil para demonstrar o fluxo de conexão entre dois pares. Vamos cobrir a mecânica necessária para conectar, transmitir e receber dados, mas vamos deixar para outro exemplo os detalhes sobre como localizar e se conectar a um computador remoto.

O HTML
------

Primeiro, vamos dar uma olhada rápida no HTML que é necessário. Não há nada incrivelmente complicado aqui. Primeiro, temos um par de botões para estabelecer e fechar a conexão:


```html
<button id="connectButton" name="connectButton" class="buttonleft">
  Conectar
</button>
<button
 id="disconnectButton"
 name="disconnectButton"
 class="buttonright"
 disabled>
  Desconectar
</button>
```


Depois, há uma caixa que contém o campo de *input* no qual o usuário pode digitar uma mensagem para transmitir, com um botão para enviar o texto digitado. Este `<div>` será o primeiro ponto (peer) no canal.


```html
<div class="messagebox">
  <label for="message"
 >Insira a mensagem:
    <input
 type="text"
 name="message"
 id="message"
 placeholder="Texto da mensagem"
 inputmode="latin"
 size="60"
 maxlength="120"
 disabled />
  </label>
  <button id="sendButton" name="sendButton" class="buttonright" disabled>
    Enviar
  </button>
</div>
```


Finalmente, há uma pequena caixa na qual vamos inserir as mensagens. Este bloco `<div>` será o segundo ponto do par (peer).


```html
<div class="messagebox" id="receivebox">
  <p>Mensagens recebidas:</p>
</div>
```

O código JavaScript
-------------------

Como você pode simplesmente ver o próprio código no GitHub, abaixo, analisaremos as partes do código que fazem o trabalho pesado.

A WebRTC API faz um intenso uso de `Promise`s. Que tornam muito fácil encadear as etapas do processo de conexão; Se você ainda não leu sobre esta funcionalidade do ECMAScript 2015, você deveria ler sobre eles. Da mesma forma, este exemplo usa arrow functions para simplificar a sintaxe.

### Começando

Quando o script é executado, configuramos um `load (en-US)` ouvinte de eventos (event listener), De modo que, uma vez que a página esteja totalmente carregada, nossa função `startup()` seja chamada.


```js
function startup() {
  connectButton = document.getElementById("connectButton");
  disconnectButton = document.getElementById("disconnectButton");
  sendButton = document.getElementById("sendButton");
  messageInputBox = document.getElementById("message");
  receiveBox = document.getElementById("receivebox");

  // Define os ouvintes de eventos para os elementos da interface do usuário

  connectButton.addEventListener("click", connectPeers, false);
  disconnectButton.addEventListener("click", disconnectPeers, false);
  sendButton.addEventListener("click", sendMessage, false);
}
```


Isso é bastante direto. Pegamos referências de todos os elementos da página que precisaremos acessar e, em seguida, configuramos `event listeners` nos três botões.

### Estabelecendo uma conexão

Quando o usuário clica no botão "Conectar", o método `connectPeers()` é chamado. Para que fique mais claro, iremos quebrar o código em mais partes, e analisar um pouco de cada vez.

**Nota:** Mesmo que ambas as extremidades da nossa conexão estejam na mesma página, vamos nos referir ao ponto que inicia a conexão como "local", e ao outro como sendo o "remoto".

#### Configurando o ponto local (local peer)


```js
localConnection = new RTCPeerConnection();

sendChannel = localConnection.createDataChannel("sendChannel");
sendChannel.onopen = handleSendChannelStatusChange;
sendChannel.onclose = handleSendChannelStatusChange;
```


O primeiro passo é criar o ponto "local" da conexão. Este é o ponto que enviará o pedido de conexão. O próximo passo é criar o `RTCDataChannel` chamando `RTCPeerConnection.createDataChannel()` (en-US) e configurando ouvintes de eventos (event listeners) para monitorar o canal, e para que possamos saber quando ele for aberto e fechado (isto é, quando o canal está conectado ou desconectado dentro dessa conexão entre pares (peer connection)).

É importante ter em mente que cada extremidade do canal tem seu próprio objeto `RTCDataChannel` .

#### Configurando o ponto remoto (remote peer)


```js
remoteConnection = new RTCPeerConnection();
remoteConnection.ondatachannel = receiveChannelCallback;
```


O ponto remoto está configurado de forma semelhante, exceto que não precisamos nós mesmos criar explicitamente um `RTCDataChannel` , uma vez que vamos ser conectados através do canal estabelecido acima. Em vez disso, criamos um `datachannel` manipulador de eventos (event handler); Isso será chamado quando o canal de dados (data channel) for aberto; Este manipulador (handler) receberá um objeto `RTCDataChannel`; você verá isso abaixo.

#### Configurando ICE candidates

O próximo passo é configurar cada conexão com os ouvintes do ICE que serão chamados quando houver um novo candidato ICE para comunicar com o outro lado.

**Nota:** Em um cenário do mundo real em que os dois pares não estão sendo executados no mesmo contexto, o processo é um pouco mais complexo; Cada lado fornece, um de cada vez, um sugestão sobre como conectar (por exemplo, UDP, UDP com um relay, TCP, etc.) chamando `RTCPeerConnection.addIceCandidate()` (en-US), e eles vão de um lado para outro até chegarem a um acordo. Mas aqui, acabamos de aceitar a primeira oferta de cada lado, uma vez que não existe uma rede real envolvida.


```js
localConnection.onicecandidate = (e) =>
  !e.candidate ||
  remoteConnection.addIceCandidate(e.candidate).catch(handleAddCandidateError);

remoteConnection.onicecandidate = (e) =>
  !e.candidate ||
  localConnection.addIceCandidate(e.candidate).catch(handleAddCandidateError);
```


Configuramos cada `RTCPeerConnection` (en-US) para ter um manipulador de eventos (event handler) para o evento `icecandidate` .

#### Iniciando a tentativa de conexão

A última coisa que precisamos fazer para começar a conectar nossos pares é criar uma oferta de conexão.


```js
localConnection
  .createOffer()
  .then((offer) => localConnection.setLocalDescription(offer))
  .then(() =>
    remoteConnection.setRemoteDescription(localConnection.localDescription),
  )
  .then(() => remoteConnection.createAnswer())
  .then((answer) => remoteConnection.setLocalDescription(answer))
  .then(() =>
    localConnection.setRemoteDescription(remoteConnection.localDescription),
  )
  .catch(handleCreateDescriptionError);
```


Vamos passar por isto linha por linha e decifrar o que significa.

1. Primeiro chamamos `RTCPeerConnection.createOffer()` (en-US) método para criar um resumo SDP (Session Description Protocol) descrevendo a conexão que queremos fazer. Este método aceita, opcionalmente, um objeto com restrições a serem suportadas pela conexão para atender às suas necessidades, como no caso da conexão precisar suportar áudio, vídeo ou ambos. Em nosso exemplo simples, não temos restrições.
2. Se a oferta for criada com sucesso, passamos o resumo junto ao método de conexões locais `RTCPeerConnection.setLocalDescription()` (en-US) . Isso configura o ponto local da conexão.
3. O próximo passo é conectar o ponto local ao remoto comunicando ao ponto remoto sobre ele. Isso é feito chamando `remoteConnection.``RTCPeerConnection.setRemoteDescription()` (en-US). Agora o `remoteConnection` conhece a conexão que está sendo construída.
4. Isso significa que é hora do ponto remoto responder. Ele faz isso chamando o método `createAnswer()` (en-US) . Isso gera um resumo de SDP que descreve a conexão que o ponto remoto está disposto e capaz de estabelecer. Essa configuração está em algum lugar na união das opções que ambos os pares podem suportar.
5. Uma vez que a resposta foi criada, ela é passada para o *remoteConnection* chamando `RTCPeerConnection.setLocalDescription()` (en-US). Isso estabelece o ponto remoto da conexão (que, para o ponto remoto, é o ponto local. Isso pode parecer confuso, mas você irá se acostumar com isso.
6. Finalmente, a descrição local das conexões remotas está configurada para se referir ao ponto remoto, chamando localConnection's `RTCPeerConnection.setRemoteDescription()` (en-US).
7. O `catch()` chama uma rotina que lida com os erros que ocorrem.

**Nota:** Mais uma vez, esse processo não é uma implementação do mundo real; No uso normal, há dois pedaços de código executados em duas máquinas, interagindo e negociando a conexão.

#### Manipulação da conexão de pares bem sucedida

Como cada lado da conexão peer-to-peer é conectado com sucesso, o evento correspondente `RTCPeerConnection` (en-US)'s `icecandidate` é disparado. Esses manipuladores podem fazer o que for necessário, mas, neste exemplo, tudo o que precisamos fazer é atualizar a interface do usuário:


```js
function handleLocalAddCandidateSuccess() {
  connectButton.disabled = true;
}

function handleRemoteAddCandidateSuccess() {
  disconnectButton.disabled = false;
}
```


A única coisa que fazemos aqui é desativar o botão "Conectar" quando o ponto local estiver conectado e ativar o botão "Desconectar" quando o ponto remoto se conectar.

#### Conectando o canal de dados

Uma vez que o `RTCPeerConnection` (en-US) é aberto, o evento `datachannel` é enviado para o ponto remoto para completar o processo de abertura do canal de dados; Isso invoca nosso método `receiveChannelCallback()`, que se parece com isso:


```js
function receiveChannelCallback(event) {
  receiveChannel = event.channel;
  receiveChannel.onmessage = handleReceiveMessage;
  receiveChannel.onopen = handleReceiveChannelStatusChange;
  receiveChannel.onclose = handleReceiveChannelStatusChange;
}
```


O evento`datachannel` inclui, em sua propriedade de canal, uma referência a um `RTCDataChannel` Representando o ponto remoto do canal. Isso é salvo, e nós configuramos, no canal, ouvintes de eventos para os eventos que queremos manipular. Uma vez feito isso, nosso método `handleReceiveMessage()` Será chamado cada vez que os dados são recebidos pelo ponto remoto, e o método `handleReceiveChannelStatusChange()` será chamado sempre que mudar o estado da conexão do canal, para que possamos reagir quando o canal estiver totalmente aberto e quando ele for fechado.

### Lidando com as mudanças de status do canal

Ambos nossos pontos locais e remotos usam um único método para lidar com eventos que indicam alguma alteração no status da conexão do canal.

Quando o ponto local experimenta um evento aberto ou fechado, o método`handleSendChannelStatusChange()` é chamado:


```js
function handleSendChannelStatusChange(event) {
  if (sendChannel) {
    var state = sendChannel.readyState;

    if (state === "open") {
      messageInputBox.disabled = false;
      messageInputBox.focus();
      sendButton.disabled = false;
      disconnectButton.disabled = false;
      connectButton.disabled = true;
    } else {
      messageInputBox.disabled = true;
      sendButton.disabled = true;
      connectButton.disabled = false;
      disconnectButton.disabled = true;
    }
  }
}
```


Se o estado do canal mudou para "open", isso indica que terminamos de estabelecer o link entre os dois pares. A interface do usuário é atualizada de forma correspondente: ativando o elemento de input de texto para a mensagem a ser enviada, focando este elemento de input para que o usuário comece imediatamente a digitar, habilitando os botões "Enviar" e "Desconectar", que são agora utilizáveis, E desativando o botão "Conectar", uma vez que não é necessário quando a conexão está aberta.

Se o estado do canal mudou para "closed", ocorre o conjunto oposto de ações: a caixa de entrada de texto e o botão "Enviar" estão desativados, o botão "Conectar" está habilitado para que o usuário possa abrir uma nova conexão se desejar, e o botão "Desconectar" está desativado, uma vez que não é útil quando não existe conexão.

Nosso exemplo de par remoto, por outro lado, ignora os eventos de alteração de status, exceto para registrar o evento no console:


```js
function handleReceiveChannelStatusChange(event) {
  if (receiveChannel) {
    console.log(
      "Receive channel's status has changed to " + receiveChannel.readyState,
    );
  }
}
```


O método `handleReceiveChannelStatusChange()` recebe como parâmetro de entrada o evento que ocorreu; Este será um `RTCDataChannelEvent` (en-US).

### Enviando mensagens

Quando o usuário pressiona o botão "Enviar", o método sendMessage() que estabelecemos como o manipulador para o evento do botão `click (en-US)` é chamado. Esse método é bastante simples:


```js
function sendMessage() {
  var message = messageInputBox.value;
  sendChannel.send(message);

  messageInputBox.value = "";
  messageInputBox.focus();
}
```


Primeiro, o texto da mensagem é obtido dos atributos do elemento de *input* `value` . Isso é enviado para o ponto remoto, ligando para `sendChannel.send()` (en-US). E está tudo aí! O resto deste método é apenas um pouco de açúcar para experiência do usuário - a caixa de entrada é esvaziada e re-focada para que o usuário comece imediatamente a digitar outra mensagem.

### Recebendo mensagens

Quando ocorre um evento de "mensagem" no canal remoto, nosso método `handleReceiveMessage()` é chamado como o manipulador de eventos.


```js
function handleReceiveMessage(event) {
  var el = document.createElement("p");
  var txtNode = document.createTextNode(event.data);

  el.appendChild(txtNode);
  receiveBox.appendChild(el);
}
```


Este método simplesmente executa alguns injeções básicas DOM ; cria um novo `<p>` (paragraph) elemento, então cria um novo nó `Text` (en-US) contendo o texto da mensagem, que é recebido na propriedade de dados do evento. Este nó de texto é anexado como um filho do novo elemento, que é então inserido no bloco *receiveBox*, fazendo com que ele desenhe na janela do navegador.

### Desconectando os pares (peers)

Quando o usuário clica no botão "Desconectar", o método `disconnectPeers()` previamente configurado como o manipulador desse botão é chamado.


```js
function disconnectPeers() {
  // Close the RTCDataChannels if they're open.

  sendChannel.close();
  receiveChannel.close();

  // Close the RTCPeerConnections

  localConnection.close();
  remoteConnection.close();

  sendChannel = null;
  receiveChannel = null;
  localConnection = null;
  remoteConnection = null;

  // Update user interface elements

  connectButton.disabled = false;
  disconnectButton.disabled = true;
  sendButton.disabled = true;

  messageInputBox.value = "";
  messageInputBox.disabled = true;
}
```


Isso começa por fechar cada par `RTCDataChannel`, então, de forma semelhante, cada um `RTCPeerConnection` (en-US). Então, todas as referências salvas desses objetos são definidas como null para evitar a reutilização acidental, e a interface do usuário é atualizada para refletir o fato de que a conexão foi fechada.

Próximos passos
---------------

Você poderia tentar este exemplo e dar uma olhada no código fonte webrtc-simple-datachannel, disponível no GitHub.