socket.io, webrtc, nodejs video chat app getting errors over https: ERR_SSL_PROTOCOL_ERROR, 404 (Not Found), and ERR_CONNECTION_TIMED_OUT
Asked Answered
R

0

0

I have put together a video chat app using socket.io, webrtc, nodejs from this online tutorial from github but now I am getting errors when converting it to be used over https:

Request URL:https://telemed.caduceususa.com/socket.io/?EIO=3&transport=polling&t=1479396416422-0
Request Method:GET
Status Code:404 Not Found
Remote Address:10.9.2.169:443

Other errors I have gotten in this process are as follows:

When I try to declare a different PORT I get - ERR_SSL_PROTOCOL_ERROR,

When I try to declare port 80 or 8080 i get: ERR_CONNECTION_TIMED_OUT

Something is going wrong on this line inside socket.io.js:

xhr.send(this.data);

I am running a node.js server on Windows Server 2012 and I have set up IIS to serve up the server on PORT 80. I have created the subdomain https://telemed.caduceususa.com in DNS and have purchased a trusted SSL cert to run the site over HTTPS.

Here is the excerpt of code from the dev tools that contains the above line that is causing the error as well as my other code:

/**
 * Creates the XHR object and sends the request.
 *
 * @api private
 */

Request.prototype.create = function(){
  var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR };

  // SSL options for Node.js client
  opts.pfx = this.pfx;
  opts.key = this.key;
  opts.passphrase = this.passphrase;
  opts.cert = this.cert;
  opts.ca = this.ca;
  opts.ciphers = this.ciphers;
  opts.rejectUnauthorized = this.rejectUnauthorized;

  var xhr = this.xhr = new XMLHttpRequest(opts);
  var self = this;

  try {
    debug('xhr open %s: %s', this.method, this.uri);
    xhr.open(this.method, this.uri, this.async);
    if (this.supportsBinary) {
      // This has to be done after open because Firefox is stupid
      // https://mcmap.net/q/376110/-get-binary-data-with-xmlhttprequest-in-a-firefox-extension
      xhr.responseType = 'arraybuffer';
    }

    if ('POST' == this.method) {
      try {
        if (this.isBinary) {
          xhr.setRequestHeader('Content-type', 'application/octet-stream');
        } else {
          xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8');
        }
      } catch (e) {}
    }

    // ie6 check
    if ('withCredentials' in xhr) {
      xhr.withCredentials = true;
    }

    if (this.hasXDR()) {
      xhr.onload = function(){
        self.onLoad();
      };
      xhr.onerror = function(){
        self.onError(xhr.responseText);
      };
    } else {
      xhr.onreadystatechange = function(){
        if (4 != xhr.readyState) return;
        if (200 == xhr.status || 1223 == xhr.status) {
          self.onLoad();
        } else {
          // make sure the `error` event handler that's user-set
          // does not throw in the same tick and gets caught here
          setTimeout(function(){
            self.onError(xhr.status);
          }, 0);
        }
      };
    }

    debug('xhr data %s', this.data);
    xhr.send(this.data);
  }

Here is the server.js file:

var fs = require('fs');

var hskey = fs.readFileSync('ssl/telemed_internal_server.key');
var hscert = fs.readFileSync('ssl/telemed_internal_cert.pem');
var ca = fs.readFileSync('ssl/telemed_internal_key.pem');

var credentials = {
    ca: ca,
    key: hskey,
    cert: hscert
};

var static = require('node-static');
var https = require('https');
var util = require('util');
var file = new(static.Server)();
var app = https.createServer(credentials, function (req, res) {
  file.serve(req, res);
}).listen(process.env.PORT || 80);

var io = require('socket.io').listen(app);

io.sockets.on('connection', function (socket){

  // convenience function to log server messages on the client
    function log(){
        var array = [">>> Message from server: "];
      for (var i = 0; i < arguments.length; i++) {
        array.push(arguments[i]);
      }
        socket.emit('log', array);
    }

    // when receive sdp, broadcast sdp to other user
    socket.on('sdp', function(data){
        console.log('Received SDP from ' + socket.id);
        socket.to(data.room).emit('sdp received', data.sdp);
    });

    // when receive ice candidate, broadcast sdp to other user
    socket.on('ice candidate', function(data){
        console.log('Received ICE candidate from ' + socket.id + ' ' + data.candidate);
        socket.to(data.room).emit('ice candidate received', data.candidate);
    });

    socket.on('message', function (message) {
        log('Got message:', message);
    // for a real app, would be room only (not broadcast)
        socket.broadcast.emit('message', message);
    });

    socket.on('create or join', function (room) {
        // join room
        var existingRoom = io.sockets.adapter.rooms[room];
        var clients = [];

        if(existingRoom){
            clients = Object.keys(existingRoom);
        }

        if(clients.length == 0){
            socket.join(room);
            io.to(room).emit('empty', room);
        }
        else if(clients.length == 1){
            socket.join(room);
            socket.to(room).emit('joined', room, clients.length + 1);
        }
        // only allow 2 users max per room
        else{
            socket.emit('full', room);
        }
    });

    socket.on('error', function(error){
        console.error(error);
    })

});

Here is the main.js (config) file:

//my signalling server
var serverIP = "https://telemed.caduceususa.com/";

// RTCPeerConnection Options
var server = {
    // Uses Google's STUN server
    iceServers: [{
        "url": "stun:stun4.l.google.com:19302"
    }, 
    {
        url: 'turn:numb.viagenie.ca',
        credential: 'muazkh',
        username: '[email protected]'
    }]
};
// various other development IPs
// var serverIP = "https://192.168.43.241:2013";
// var serverIP = "https://10.0.11.196:2013";

var localPeerConnection, signallingServer;

var btnSend = document.getElementById('btn-send');
var btnVideoStop = document.getElementById('btn-video-stop');
var btnVideoStart = document.getElementById('btn-video-start');
var btnVideoJoin = document.getElementById('btn-video-join');
var localVideo = document.getElementById('local-video');
var remoteVideo = document.getElementById('remote-video');

var inputRoomName = document.getElementById('room-name');

var localStream, localIsCaller;

btnVideoStop.onclick = function(e) {
    e.preventDefault();
    // stop video stream
    if (localStream != null) {
        localStream.stop();
    }

    // kill all connections
    if (localPeerConnection != null) {
        localPeerConnection.removeStream(localStream);
        localPeerConnection.close();
        signallingServer.close();
        localVideo.src = "";
        remoteVideo.src = "";
    }

    btnVideoStart.disabled = false;
    btnVideoJoin.disabled = false;
    btnVideoStop.disabled = true;
}

btnVideoStart.onclick = function(e) {
    e.preventDefault();
    // is starting the call
    localIsCaller = true;
    initConnection();
}

btnVideoJoin.onclick = function(e) {
    e.preventDefault();
    // just joining a call, not offering
    localIsCaller = false;
    initConnection();
}

function initConnection() {
    var room = inputRoomName.value;

    if (room == undefined || room.length <= 0) {
        alert('Please enter room name');
        return;
    }

    // start connection!
    connect(room);

    btnVideoStart.disabled = true;
    btnVideoJoin.disabled = true;
    btnVideoStop.disabled = false;
}


// WEBRTC STUFF STARTS HERE
// Set objects as most are currently prefixed
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection ||
    window.webkitRTCPeerConnection || window.msRTCPeerConnection;
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription ||
    window.webkitRTCSessionDescription || window.msRTCSessionDescription;
navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia ||
    navigator.webkitGetUserMedia || navigator.msGetUserMedia;
window.SignallingServer = window.SignallingServer;

var sdpConstraints = {
    optional: [],
    mandatory: {
        OfferToReceiveVideo: true,
    }
}

function connect(room) {
    // create peer connection
    localPeerConnection = new RTCPeerConnection(server);

    // create local data channel, send it to remote
    navigator.getUserMedia({
        video: true,
        audio: true
    }, function(stream) {
        // get and save local stream
        trace('Got stream, saving it now and starting RTC conn');

        // must add before calling setRemoteDescription() because then 
        // it triggers 'addstream' event
        localPeerConnection.addStream(stream);
        localStream = stream;

        // show local video
        localVideo.src = window.URL.createObjectURL(stream);

        // can start once have gotten local video
        establishRTCConnection(room);

    }, errorHandler)
}

function establishRTCConnection(room) {
    // create signalling server
    signallingServer = new SignallingServer(room, serverIP);
    signallingServer.connect();


    // a remote peer has joined room, initiate sdp exchange
    signallingServer.onGuestJoined = function() {
        trace('guest joined!')
        // set local description and send to remote
        localPeerConnection.createOffer(function(sessionDescription) {
            trace('set local session desc with offer');

            localPeerConnection.setLocalDescription(sessionDescription);

            // send local sdp to remote
            signallingServer.sendSDP(sessionDescription);
        });
    }

    // got sdp from remote
    signallingServer.onReceiveSdp = function(sdp) {
        // get stream again
        localPeerConnection.addStream(localStream);
        trace(localStream)

        // if local was the caller, set remote desc
        if (localIsCaller) {
            trace('is caller');
            trace('set remote session desc with answer');
            localPeerConnection.setRemoteDescription(new RTCSessionDescription(
                sdp));
        }
        // if local is joining a call, set remote sdp and create answer
        else {
            trace('set remote session desc with offer');
            localPeerConnection.setRemoteDescription(new RTCSessionDescription(
                sdp), function() {
                trace('make answer')
                localPeerConnection.createAnswer(function(
                    sessionDescription) {
                    // set local description
                    trace('set local session desc with answer');
                    localPeerConnection.setLocalDescription(
                        sessionDescription);

                    // send local sdp to remote too
                    signallingServer.sendSDP(sessionDescription);
                });
            });
        }
    }

    // when received ICE candidate
    signallingServer.onReceiveICECandidate = function(candidate) {
        trace('Set remote ice candidate');
        localPeerConnection.addIceCandidate(new RTCIceCandidate(candidate));
    }

    // when room is full, alert user
    signallingServer.onRoomFull = function(room) {
        window.alert('Room "' + room +
            '"" is full! Please join or create another room');
    }

    // get ice candidates and send them over
    // wont get called unless SDP has been exchanged
    localPeerConnection.onicecandidate = function(event) {
        if (event.candidate) {
            //!!! send ice candidate over via signalling channel
            trace("Sending candidate");
            signallingServer.sendICECandidate(event.candidate);
        }
    }

    // when stream is added to connection, put it in video src
    localPeerConnection.onaddstream = function(data) {
        remoteVideo.src = window.URL.createObjectURL(data.stream);
    }

}

function errorHandler(error) {
    console.error('Something went wrong!');
    console.error(error);
}

function trace(text) {
    console.info(text);
}

Here is the signalling server:

function trace(text){
    console.info(text);
}

// Connects to signalling server with given room and IP
// has methods to exchange SDP and ICE candidates

var SignallingServer = function(room, socketServer){
    this.room = room;
    this.socket = io.connect(socketServer);
    this.socket.on('full', function (room){
      trace('Room ' + room + ' is full');
      this.onRoomFull(room);
    }.bind(this));

    this.socket.on('empty', function (room){
      this.isInitiator = true;
      trace('Room ' + room + ' is empty');
    });

    this.socket.on('join', function (room){
      trace('Making request to join room ' + room);
    });

    this.socket.on('joined', function (room, numClients){
      trace('New user has joined ' + room);
      trace('Room has ' + numClients + ' clients');
      //ask host to initiate sdp transfer
      this.onGuestJoined();
    }.bind(this));

    this.socket.on('sdp received', function(sdp){
        trace('Received SDP ');
        trace(sdp);
        this.onReceiveSdp(sdp);
    }.bind(this));

    this.socket.on('ice candidate received', function(candidate){
        trace('Received ICE candidate ');
        trace(candidate);
        this.onReceiveICECandidate(candidate);
    }.bind(this));

    this.socket.on('log', function (array){
      console.log.apply(console, array);
    });
}

SignallingServer.prototype = {
    connect: function(){
        if (this.room !== '') {
          trace('Joining room ' + this.room);
          this.socket.emit('create or join', this.room);
        }
    },
    close: function(){
        trace('Disconnecting')
        this.socket.disconnect();
    },
    sendSDP: function(sdp){
        trace('sending sdp')
        this.socket.emit('sdp', {
            room: this.room,
            sdp: sdp
        });
    },
    sendICECandidate: function(candidate){
        trace('sending ice candidate');
        this.socket.emit('ice candidate', {
            room: this.room,
            candidate: candidate
        });
    },
    onReceiveSdp: function(sdp){
        trace('Placeholder function: Received SDP')
    },
    onGuestJoined: function(){
        trace('Placeholder function: Guest joined room')
    },
    onReceiveICECandidate: function(candidate){
        trace('Placeholder function: Received ICE candidate')
    },
    onRoomFull: function(room){
        trace('Placeholder function: Room is full!');
    }
}

window.SignallingServer = SignallingServer;

AND FINALLY THE HTML (CAN SOMEONE ALSO EXPLAIN WHAT LIVERELOAD.JS IS?):

<!doctype html>
<!--[if lt IE 7]>
<html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="">
  <![endif]-->
  <!--[if IE 7]>
  <html class="no-js lt-ie9 lt-ie8" lang="">
    <![endif]-->
    <!--[if IE 8]>
    <html class="no-js lt-ie9" lang="">
      <![endif]-->
      <!--[if gt IE 8]>
      <!-->
      <html class="no-js" lang="">
        <!--<![endif]-->
<head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <title></title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <link rel="stylesheet" href="css/bootstrap.min.css">
        <style>
            body {
                padding-top: 50px;
                padding-bottom: 20px;
            }
        </style>
        <link rel="stylesheet" href="css/bootstrap-theme.min.css">
        <link rel="stylesheet" href="css/main.css">

        <script src="js/vendor/modernizr-2.8.3-respond-1.4.2.min.js"></script>
</head>
<body>
        <!--[if lt IE 8]>
        <p class="browserupgrade">
          You are using an <strong>outdated</strong>
          browser. Please
          <a href="https://browsehappy.com/">upgrade your browser</a>
          to improve your experience.
        </p>
        <![endif]-->
        <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
          <div class="container">
            <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false">
              <span class="sr-only">Toggle navigation</span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
            </button>
              <a class="navbar-brand" href="#">WebRTC Video Chat</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
              <!-- chatroom name form -->
              <form class="navbar-form navbar-right form-inline">
                <div class="form-group">
                  <input class="form-control" type="text" id="room-name" placeholder="Room name"/>
                </div>
                <button class="btn btn-primary" id="btn-video-start">Start</button>
                <button class="btn btn-default"  id="btn-video-join">Join</button>
                <button class="btn btn-default"  disabled id="btn-video-stop">Stop</button>
              </form>
            </div>

            <!--/.navbar-collapse --> </div>

        </nav>

        <div class="container main">
          <div class="row videos">
            <div class="remote-video">
              <video width="280" height="250" autoplay id="remote-video"></video>
            </div>
            <div class="local-video">
              <video width="280" height="250" autoplay id="local-video" muted></video>
            </div>
          </div>
        </div>
      </div>
      <!-- /container -->
      <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
      <script>window.jQuery || document.write('<script src="js/vendor/jquery-1.11.2.min.js"><\/script>')</script>

    <script src="js/vendor/bootstrap.min.js"></script>
    <script src="js/vendor/socket.io.js"></script>

    <script src="js/main.js"></script>
    <script src="js/signalling.js"></script>

    <script src="//localhost:9010/livereload.js"></script>

</body>
  </html>
Rathe answered 17/11, 2016 at 15:54 Comment(3)
How are IIS and Node interconnected? Is IIS serving as a reverse proxy for the Node server? Also, HTTPS should really be served from port 443, not port 80.Gardel
Will IISNode be able to support ssl/port 443/etc.? Is the reverse proxy the only way?Rathe
I gave up on the reverse proxy and applied an iisNode solution and was able to get it up and running.Rathe

© 2022 - 2024 — McMap. All rights reserved.