How to determine WebRTC dataChannel.send is delivered?

Viewed 1022

I am trying to write a chat application using WebRTC and I can send the messages over the dataChannel using a code like bellow one:

const peerConnection = new RTCPeerConnection();

const dataChannel =
  peerConnection.createDataChannel("myLabel", dataChannelOptions);

dataChannel.onerror = (error) => {
  console.log("Data Channel Error:", error);
};

dataChannel.onmessage = (event) => {
  console.log("Got Data Channel Message:", event.data);
};

dataChannel.onopen = () => {
  dataChannel.send("Hello World!");
};

dataChannel.onclose = () => {
  console.log("The Data Channel is Closed");
};

with dataChannel.send() I can send data over the channel correctly. but I am wondering to know, is there any way to determine that the sent message is delivered to another side or not?

1 Answers

This simplest answer is: send a reply.

But you may not need to, if you use an ordered, reliable datachannel (which is the default).

An ordered reliable datachannel

With one of these, you determine a message was sent, by waiting for the bufferedAmount to go down:

const pc1 = new RTCPeerConnection(), pc2 = new RTCPeerConnection();
const channel = pc1.createDataChannel("chat");

chat.onkeypress = async e => {
  if (e.keyCode != 13) return;

  const before = channel.bufferedAmount;
  channel.send(chat.value);

  const after = channel.bufferedAmount;
  console.log(`Queued ${after - before} bytes`);

  channel.bufferedAmountLowThreshold = before; // set floor trigger and wait
  await new Promise(r => channel.addEventListener("bufferedamountlow", r));

  console.log(`Sent ${after - channel.bufferedAmount} bytes`);
  chat.value = "";
};

pc2.ondatachannel = e => e.channel.onmessage = e => console.log(`> ${e.data}`);
pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
pc1.oniceconnectionstatechange = e => console.log(pc1.iceConnectionState);
pc1.onnegotiationneeded = async e => {
  await pc1.setLocalDescription(await pc1.createOffer());
  await pc2.setRemoteDescription(pc1.localDescription);
  await pc2.setLocalDescription(await pc2.createAnswer());
  await pc1.setRemoteDescription(pc2.localDescription);
}
Chat: <input id="chat"><br>

Since the channel is reliable, it won't give up sending this message until it's been received.

Since the channel is ordered, it won't send a second message before this message has been received.

This lets you send a bunch of messages in a row without waiting on a reply. As long as the bufferedAmount keeps goes down, you know it's being sent and received.

In short, to determine a message was received, send a second message, or have the other side send a reply.

An unreliable datachannel

If you're using an unreliable datachannel, then sending a reply is the only way. But since there's no guarantee the reply will make it back, this may produce false negatives, causing duplicate messages on the receiving end.

Unreliable one way, reliable the other

Using the negotiated constructor argument, it's possible to create a datachannel that's unreliable in one direction, yet reliable in the other. This can be used to solve the unreliable reply, to avoid duplicate messages on the receiving (pc2) end.

dc1 = pc1.createDataChannel("chat", {negotiated: true, id: 0, maxRetransmits: 0});
dc2 = pc2.createDataChannel("chat", {negotiated: true, id: 0});
Related