title: Simple secure websocket (wss) chat using Node.js server and Jquery client
body: <p>I have added voice to this chat, so it is now a combined voice/text chat. You can find my contribution up on github at the following URL:</p>
  
  <p><a href="https://github.com/HowardRichman/simple-text-voice-chat-in-node-and-javascript" rel="nofollow noreferrer">https://github.com/HowardRichman/simple-text-voice-chat-in-node-and-javascript</a></p>
  
  <p>Here is a working example of a secure websocket chat using a Node.js server and a jquery javascript client in Centos 6.9. There are only two files involved: (1) server.js and (2) client.htm. </p>
  
  <p>Here is the code for server.js which I run using the following linux command line: node server.js</p>
  
  <pre><code>const fs = require('fs');
  const https = require('https');
  const WebSocket = require('ws');
  
  const server = new https.createServer({
    cert: fs.readFileSync('/var/cpanel/ssl/apache_tls/example.com/combined'),
    key: fs.readFileSync('/var/cpanel/ssl/apache_tls/example.com/combined')
  });
  const wss = new WebSocket.Server({ server });
  var msg;
  
  wss.on('connection', function connection(ws) 
  {
    ws.on('message', function incoming(message) 
    {
      msg = message;
      console.log('received: %s', msg);
      wss.clients.forEach(function (client) 
      {
         if (client.readyState == WebSocket.OPEN) 
         {
            client.send( msg );
         }
      });
    });
  
    ws.send('Chat room is working!');
  });
  
  server.listen(8089);
  </code></pre>
  
  <p>Notes: </p>
  
  <ol>
  <li><p>You must change the paths in the above to the paths to <em>your</em> secure certificate and key. I found these paths in my server's httpd.conf file. </p></li>
  <li><p>Optionally, you can change the port from 8089 in this and the client file to another available port. (Ports from 8081 to 8099 are usually available.)</p></li>
  </ol>
  
  <p>Here's the code for client.htm file which should be accessed using an https URL (e.g., <a href="https://example.com/client.htm" rel="nofollow noreferrer">https://example.com/client.htm</a>):</p>
  
  <pre><code>&lt;!DOCTYPE html&gt;
  &lt;html&gt;
  &lt;head&gt;
  &lt;TITLE&gt;Chat Client&lt;/TITLE&gt;
  &lt;/head&gt;
  &lt;body&gt;
  &lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"&gt;&lt;/script&gt;
  &lt;script&gt;
  var webSocket;
  var firstTime = true;
  
  function connectWsChat()
  {
    try 
    {
      window.WebSocket = window.WebSocket || window.MozWebSocket;
      var host = 'wss://example.com:8089/';
      webSocket = new WebSocket( host );
       webSocket.onopen = function() 
       {
        var chatUser = $( '#chatUser' ).val();
        webSocket.send( '&lt;p&gt;' + chatUser + ' has entered the chat room' );
         $( '#chatButton' ).text( 'Disconnect' );
       }
       webSocket.onmessage = function( msg ) 
       {
          logWsChatMessage( '&lt;p class="message"&gt;' + msg.data + '&lt;/p&gt;' );
       }
       webSocket.onclose = function() 
       {
        var chatUser = $( '#chatUser' ).val();
         logWsChatMessage( '&lt;p&gt;' + chatUser + ' has left the chat room&lt;/p&gt;' );
         // $( '#chatButton' ).text( 'Connect' );
       }
     } 
     catch( exception ) 
     {
        logWsChatMessage( '&lt;p&gt;Error ' + exception + '.&lt;/p&gt;' );
     }
  }
  
  function isConnectedWsChat() {
    if( webSocket &amp;&amp; webSocket.readyState==1 ) {
      $( '#chatButton' ).text( 'Disconnect' );
      return 1;
    }
  }
  
  function playSound(){
      var audio = new Audio('notify.mp3');
      audio.play();   
  }
  
  
  function sendWsChat() {
    var chatLog = $( '#chatLog' );
    if( isConnectedWsChat() ) {
      var chatUser = $( '#chatUser' ).val();
      var chatText = $( '#chatText' ).val();
      if( chatUser=='' || chatText=='' ){
        return;
      }
      try{
        chatLog.scrollTop( chatLog.prop( 'scrollHeight' ) );
        webSocket.send( chatUser + ': ' + chatText );
        //logWsChatMessage( '&lt;p class="event"&gt;Sent: ' + chatText + '&lt;/p&gt;' )
      } catch( exception ){
        logWsChatMessage( '&lt;p class="warning"&gt; Error: ' + exception + '&lt;/p&gt;' );
      }
      $( '#chatText' ).val( '' );
    }
  }
  
  function logWsChatMessage(msg) {
    var chatLog = $( '#chatLog' );
    var sTop = Math.round( chatLog.prop( 'scrollTop') );
    var sHeight = chatLog.prop( 'scrollHeight' );
    var cHeight = chatLog.prop( 'clientHeight' );
  
    chatLog.append( '&lt;p&gt;' + msg + '&lt;/p&gt;' );
  
    if (firstTime) 
    {
      chatLog.scrollTop( chatLog.prop( 'scrollHeight' ) );
      firstTime = false;
    } 
    else if (sTop + cHeight == sHeight ) 
    {
      chatLog.scrollTop( chatLog.prop( 'scrollHeight' ) );
    }
    playSound();
  }
  
  $(document).ready( function() {
  
    if( !( 'WebSocket' in window ) ) {
      $( '#chatInput').fadeOut( 'fast' );
      $( '&lt;p&gt;Oh no, you need a browser that supports WebSockets.&lt;/p&gt;' )
          .appendTo( '#chatContainer' );
    } else {
      connectWsChat();
    }
  
    $( '#chatText' ).keypress( function( event ) {
      if( event.keyCode == '13' ) {
        sendWsChat();
      }
    });
  
    $( '#startButton' ).click( function() {
       window.open('chat.pl','_self');
    });
  
    $( '#saveButton' ).click( function() {
       window.open('chat1.pl','_self');
    });
  
  
    $( '#chatButton' ).click( function() {
      if( webSocket &amp;&amp; webSocket.readyState==1 ) {
        var chatUser = $( '#chatUser' ).val();
        webSocket.send( '&lt;p&gt;' + chatUser + ' has left the chat room.&lt;/p&gt;' );
        webSocket.close();
        $( this ).text( 'Connect' );
      } else {
        //webSocket.open();
        connectWsChat();
        $( this ).text( 'Disconnect' );
      }
    });
  
    $( window ).on ("unload", function(e) {
      if( webSocket &amp;&amp; webSocket.readyState==1 ) {
        var chatUser = $( '#chatUser' ).val();
        webSocket.send( '&lt;p&gt;' + chatUser + ' has left the chat room.&lt;/p&gt;' );
        webSocket.close();
      }
    });
  
  });
  &lt;/script&gt;
  &lt;div id="chatContainer"&gt;
  &lt;div id="chatLog"&gt;
  &lt;/div&gt;
  &lt;div id="chatInput"&gt;
  &lt;p&gt;Name: &lt;input id='chatUser' type="text" size=40 /&gt;
  &lt;br&gt;Text: &lt;input id="chatText" type="text" size=40 /&gt;
  &lt;br&gt;&lt;button id="chatButton"&gt;Chat not yet set up&lt;/button&gt;
  &lt;/p&gt;
  &lt;/div&gt;
  &lt;/div&gt;
  &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>
  
  <p>Notes: </p>
  
  <ol>
  <li><p>Change example.com to the name of your website.</p></li>
  <li><p>I have only tested this code in Chrome and Firefox. In Firefox the user's name automatically appears in the name box when the user enters the chat if he or she has been in the chat room before. That doesn't happen in Chrome, but you could probably use a cookie to make sure that the name appears in Chrome.</p></li>
  <li><p>The user posts by clicking "Enter".</p></li>
  <li><p>The sound file notify.mp3 is a beep that plays whenever a new post arrives from the server. I have not included that file here. </p></li>
  <li><p>The commands regarding scroll and client height allow the user to scroll up to read earlier postings in a fast-moving chat without being forced to the bottom whenever new messages come in. My css file (not included) includes a max-height command for the chatLog division which may be necessary to get this to work Here's part of my css code: #chatLog {max-height: 430px; overflow-y: auto; width:100%}.</p></li>
  </ol>
  
  <p>My Question:</p>
  
  <p>Shortly before I got this chat working, I was working on a different solution and loaded mod_proxy_wstunnel.so in my Apache configuration file. I don't know whether that was necessary.</p>
  
  <p>My question is: Can you get a secure websocket chat working if you don't have mod_proxy_wstunnel.so loaded?</p>