File: public/js/websockets.js

Recommend this page to a friend!
  Classes of Johnny Mast  >  PHP MySQL WebSocket Chat  >  public/js/websockets.js  >  Download  
File: public/js/websockets.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: PHP MySQL WebSocket Chat
Websocket chat that stores messages in MySQL
Author: By
Last change:
Date: 6 months ago
Size: 9,125 bytes
 

Contents

Class file image Download
/* eslint-disable no-undef */
/**
 * For more information about the protocol you can read to protocol information
 * document at our wiki.
 *
 * @see {@link https://github.com/johnnymast/mysql_websocket_chat/wiki/Protocol|GitHub}
 */

const RECONNECT_IN_SEC = 10
const ws = {
  /**
   * Start the connection
   * @type {WebSocket}
   */
  conn: null
}

/**
 * Handle automatically reconnecting to a websocket server
 * if the connection was interrupted.
 *
 * @param {function} callback - The callback called upon reconnection
 */
WebSocket.prototype.reconnect = (callback) => {
  if (this.readyState === WebSocket.OPEN || this.readyState !== WebSocket.CONNECTING) {
    this.close()
  }

  let seconds = RECONNECT_IN_SEC
  const container = dom('.connection_alert .error_reconnect_countdown')
  const countHandle = setInterval(() => {
    if (--seconds <= 0) {
      clearInterval(countHandle)
      callback()
      return
    }
    container.text(seconds.toString())
  }, 1000)
}

/**
 * Connect to the websocket server.
 */
const connect = () => {
  if (ws.conn) {
    if (ws.conn.readyState === WebSocket.OPEN || ws.conn.readyState === WebSocket.CONNECTING) {
      ws.conn.close()
    }
    delete ws.conn
  }

  ws.conn = new WebSocket('ws://' + socketHost + ':' + socketPort)

  /**
   * Connection has been established
   *
   * @param {Event} event - The onopen event.
   */
  ws.conn.onopen = (event) => {
    dom('.client_chat').removeAttr('disabled')
    dom('.connection_alert').hide()

    /**
     * Register te client to the
     * server. This allows the server
     * to return a list of chat clients
     * to list on the side.
     */
    registerClient()

    /**
     * Request the user list from
     * the server. If the server replies the user list
     * will be populated.
     */
    requestUserlist()
  }

  /**
   * A new message (read package) has been received.
   *
   * @param {Event} event - The onmessage event.
   */
  ws.conn.onmessage = (event) => {
    const pkg = JSON.parse(event.data)

    if (pkg.type === 'message') {
      dialogOutput(pkg)
    } else if (pkg.type === 'userlist') {
      usersOutput(pkg.users)
    } else if (pkg.type === 'typing') {
      typingOutput(pkg)
    }
  }

  /**
   * Notify the user that the connection is closed
   * and disable the chat bar.
   *
   * @param {Event} event - The onclose event.
   */
  ws.conn.onclose = (event) => {
    console.log('Connection closed!')

    dom('.client_chat').prop('disabled', true)
    dom('.connection_alert').show()
    clearUserlist()

    if (event.target.readyState === WebSocket.CLOSING || event.target.readyState === WebSocket.CLOSED) {
      event.target.reconnect(connect)
    }
  }

  /**
   * Display a message in the terminal if
   * we run into an error.
   *
   * @param {Event} event - The error event.
   */
  ws.conn.onerror = (event) => {
    console.log('We have received an error!', event)
  }
}

/**
 * Remove all users from the users on the
 * side of the screen.
 */
clearUserlist = () => {
  /**
   * First of all clear the current userlist
   */
  while (userList.firstChild) {
    userList.removeChild(userList.firstChild)
  }
}

/**
 * Put a package (message) on the screen
 * for you or others to read.
 *
 * @param {object} pkg - The package object to display.
 */
dialogOutput = (pkg) => {
  if (pkg.to_user) {
    if (pkg.to_user.id === chat_user.id) {
      dom('.chat_dialog').append('<b class="priv_msg">(Private from &lt;&lt; ' + pkg.user.username + '</b>)  ' + pkg.message + '<br/>')
    } else {
      dom('.chat_dialog').append('<b class="priv_msg">(Private to &gt;&gt; ' + pkg.to_user.username + '</b>): ' + pkg.message + '<br/>')
    }
  } else {
    dom('.chat_dialog').append('<b>' + pkg.user.username + '</b>: ' + pkg.message + '<br/>')
  }
}

/**
 * Update the user list in the UI
 *
 * @param {array} users - Array of uses to display in the chatroom.
 */
usersOutput = (users) => {
  /**
   * First get the current select value
   * on the list. This is so we can restore
   * the selected list item after requesting
   * fow new users.
   */
  const selectedUser = userList.value

  /**
   * Before we start adding users
   * to the userlist make sure we erase
   * all the old users of the screen.
   */
  clearUserlist()

  for (const index in users) {
    if (typeof users[index] !== 'undefined') {
      const user = users[index]
      const elm = document.createElement('OPTION')

      elm.value = user.id
      elm.appendChild(document.createTextNode(user.username))

      if (elm.value === chat_user.id) {
        elm.classList = ['client_user_you']
        elm.disabled = 'disabled'
      }

      if (selectedUser.length > 0 && elm.value === selectedUser) {
        elm.selected = 'selected'
      }

      userList.appendChild(elm)
    }
  }
}

/**
 * Display a message on the screen indicating some is typing a message.
 *
 * @param {object} pkg - The received package from the server
 */
typingOutput = (pkg) => {
  if (typeof pkg === 'object') {
    const user = pkg.user
    const isTyping = pkg.value

    const indicator = dom('.typing_indicator').get()
    const typingMessage = dom(`.typing_indicator li[data-userid="${user.id}"]`).get()

    if (typingMessage) {
      typingMessage.parentNode.removeChild(typingMessage)
    }

    if (isTyping) {
      const msg = `${user.username} is typing a message`
      const li = document.createElement('LI')
      li.dataset.userid = user.id
      li.innerText = msg

      indicator.appendChild(li)
    }
  }
}

/**
 * We need to register this browser window (client)
 * to the server. We do this so we can sent private
 * messages to other users.
 */
registerClient = () => {
  /**
   * Create a registration package to send to the
   * server.
   */
  let pkg = {
    user: chat_user, /* Defined in index.php */
    type: 'registration'
  }

  pkg = JSON.stringify(pkg)

  /**
   * Send the package to the server
   */
  if (ws.conn && ws.conn.readyState === WebSocket.OPEN) {
    ws.conn.send(pkg)
  }
}

/**
 * Request a list of current active
 * chat users. We do this every x seconds
 * so we can update the ui.
 */
requestUserlist = () => {
  setInterval(() => {
    if (ws.conn.readyState !== WebSocket.CLOSING && ws.conn.readyState !== WebSocket.CLOSED) {
      /**
       * Create a package to request the list of users
       */
      let pkg = {
        user: chat_user, /* Defined in index.php */
        type: 'userlist'
      }

      /**
       * We need a object copy of package
       * to send to dialog_output() but we
       * also want to turn the original package
       * into a string so we can send it over the
       * socket to the server.
       *
       * @type {Object}
       */
      pkg = JSON.stringify(pkg)
      if (ws.conn && ws.conn.readyState === WebSocket.OPEN) {
        ws.conn.send(pkg)
      }
    }
  }, 2000)
}

/**
 * Register user is typing yes or no.
 *
 * @param {boolean} currently - Is this user currently typing?
 */
registerTyping = (currently) => {
  /**
   * Create a package to send to the
   * server.
   */
  let pkg = {
    user: chat_user, /* Defined in index.php */
    type: 'typing',
    value: currently || false
  }

  pkg = JSON.stringify(pkg)

  /**
   * Send the package to the server
   */
  if (ws.conn && ws.conn.readyState === WebSocket.OPEN) {
    ws.conn.send(pkg)
  }
}

/**
 * Send a chat message to the server
 */
sendMessage = () => {
  /**
   * Catch the chat text
   *
   * @type {string}
   */
  const chatMessage = dom('.client_chat').val()

  if (typeof chatMessage === 'undefined' || chatMessage.length === 0) {
    dom('.client_chat ').addClass('error')
    setTimeout(() => {
      dom('.client_chat ').removeClass('error')
    }, 500)
  }

  registerTyping(false)

  /**
   * When to_user is empty the
   * message will be sent to all users
   * in the chat room.
   *
   * @type {Object}
   */
  let toUser = null

  /**
   *  If a user is selected in the
   *  userlist this will mean send messages
   *  to that user.
   */
  if (userList.value) {
    toUser = {
      id: userList.value,
      username: userList.options[userList.selectedIndex].text
    }
  }

  /**
   * Create a package to send to the
   * server.
   */
  let pkg = {
    user: chat_user, /* Defined in index.php */
    message: chatMessage,
    to_user: toUser,
    type: 'message'
  }

  /**
   * We need a object copy of package
   * to send to dialog_output() but we
   * also want to turn the original package
   * into a string so we can send it over the
   * socket to the server.
   *
   * @type {Object}
   */
  const pkgObject = pkg
  pkg = JSON.stringify(pkg)

  /**
   * Send the package to the server
   */
  if (ws.conn && ws.conn.readyState === WebSocket.OPEN) {
    ws.conn.send(pkg)
  }

  /**
   * Display the message we just wrote
   * to the screen.
   */
  dialogOutput(pkgObject)

  /**
   * Empty the chat input bar
   * we don't need it anymore.
   */
  dom('.client_chat').val('')
}

const userList = dom('.user_list').get()

document.addEventListener('DOMContentLoaded', connect)
/* eslint-enable no-undef */

For more information send a message to info at phpclasses dot org.