user1914860
user1914860

Reputation: 38

Local WebRTC connection stuck on "checking" when offline

I've been trying to get a hermedic development environment such that things work locally e.g. on one computer while on the train with no internet. I've created this minimal "Hello World" page which tries to create a WebRTC connection between two things in the same page (the "creator" and "joiner"). This way the signalling server is stubbed out and the steps can be shown in one synchronous log. However I'm not not getting the callbacks that I expect when my computer is offline.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">

  <title>Offline WebRTC</title>
  <style>
  html, body {padding:0;margin:0;height:100%}
  body {box-sizing:border-box;padding:50px 0 0px;color:#ccc;background-color:#303030;}
  h1   {position:fixed;margin:0;line-height:50px;padding:0 15px;top:0;left:0;font-size:18px;width:100%;box-sizing:border-box}
  </style>
</head>

<body>
<h1>Why does this WebRTC work online but not offline?</h1>
<pre id="log"></pre>


<script type="text/javascript">

//
// Gobals
//

// This is the interface through which the Creator and Joiner communicate.
// Usually this would involve piping through the server via websockets.
const signallingServer = {
  giveOfferToJoiner: null,   // initialized in the "create" section
  giveAnswerToCreator: null, // initialized in the "join" section
};

let logCounter = 0;
function logWithIndent(message, indent) {
  const prefix = ''.padStart(indent, ' ') + (''+logCounter).padStart(4, '0') + ' ';
  logCounter += 1;
  document.getElementById('log').textContent += prefix + message + '\n';
  const panes = [
    document.getElementById('join-pane'),
    document.getElementById('create-pane'),
  ];
}


//
// Join (right column)
//
(() => {
  const log = (message) => logWithIndent(message, 50);
  const pc = new RTCPeerConnection(null);
  const sdpConstraints = { optional: [{RtpDataChannels: true}]  };



  signallingServer.giveOfferToJoiner = (offerString) => {
    log('Received offer');
    const offerDesc = new RTCSessionDescription(JSON.parse(offerString));
    pc.setRemoteDescription(offerDesc);
    pc.createAnswer(
      (answerDesc) => {
        log('Setting peer connection description')
        pc.setLocalDescription(answerDesc);
      },
      () => { log("ERROR: Couldn't create answer"); },
      sdpConstraints
    );
  };

  pc.ondatachannel  = (e) => {
    const dataChannel = e.channel;
    const sendMessage = (message) => {
      log(`Sending message: ${message}`);
      dataChannel.send(message);
    };
    dataChannel.onopen = () => { log("Data channel open!"); };
    dataChannel.onmessage = (e) => {
      const message = e.data
      log("Received message: " + message);
      sendMessage('PONG: ' + message)
    }

  };
  pc.onicecandidate = (e) => {
    if (e.candidate) {
      log('waiting for null candidate for answer');
      return;
    }
    const answer = JSON.stringify(pc.localDescription);
    log('Answer created. Sending to creator');
    signallingServer.giveAnswerToCreator(answer);
    log('waiting for connection...')
  };
  pc.oniceconnectionstatechange = (e) => {
    const state = pc.iceConnectionState;
    log(`iceConnectionState changed to "${state}"`)
    if (state == "connected") {
      log('TODO: send message');
    }
  };

  log(`Waiting for offer`);
})();


//
// Create (left)
//
(() => {
  const log = (message) => logWithIndent(message, 0);
  const pc = new RTCPeerConnection(null);

  let dataChannel = null;
  const sendMessage = (message) => {
    log(`Sending message: ${message}`);
    dataChannel.send(message);
  };

  signallingServer.giveAnswerToCreator = (answerString) => {
    var answerDesc = new RTCSessionDescription(JSON.parse(answerString));
    log('Setting peer connection description')
    pc.setRemoteDescription(answerDesc);
  };


  pc.oniceconnectionstatechange = (e) => {
    const state = pc.iceConnectionState;
    log(`iceConnectionState changed to "${state}"`)
  };
  pc.onicecandidate = (e) => {
    if (e.candidate) {
      log(`Waiting for null candidate for offer`);
      return;
    }
    const offer = JSON.stringify(pc.localDescription);
    log(`Offer created. Sending to joiner`);
    signallingServer.giveOfferToJoiner(offer);
    log(`waiting for answer...`);
  }


  function createOffer() {
    dataChannel = pc.createDataChannel("chat");
    dataChannel.onopen = () => { log("Data channel open!"); sendMessage('Hello World!')};
    dataChannel.onmessage = (e) => { log("Received message: " + e.data); }
    log('Creating offer...');
    pc.createOffer().then((e) => {
      log('setting local description');
      pc.setLocalDescription(e);
    });
  };


  createOffer();
})();
</script>

</body>
</html>

To reproduce:

  1. While connected to the internet, open this .html file locally (should have a file://... URL, no need for a server)
  2. Observe that it is working properly (should get toPONG: Hello World!)
  3. Disconnect your computer from the internet
  4. Refresh the page
  5. Observe that it does not proceed after iceConnectionState changed to "checking"

Additional info:

So my main question is: How can I open a local WebRTC connection when my computer is offline?

Additional questions: I assume that the browser is trying to talk to someone in the background as part of the check or connect step. Who is it trying to talk to? Why are these requests not showing up in the network tab of the devtools?

Upvotes: 0

Views: 1822

Answers (1)

Philipp Hancke
Philipp Hancke

Reputation: 17305

WebRTC gathers candidates from your local network interfaces as part of the ICE process. From looking at the SDP (either in the debugger or on chrome://webrtc-interals), when offline there no interface (other than the loopback interface which is ignored) to gather candidates from, there is no candidate in onicecandidate and you just send an offer without any candidates.

Going into 'checking' ICE connection state seems like a bug, https://w3c.github.io/webrtc-pc/#rtcicetransportstate requires a remote candidate for that.

Upvotes: 1

Related Questions