├── .stylelintrc.json ├── .github ├── FUNDING.yml ├── workflows │ ├── StyleLint.yaml │ ├── Phpcs.yaml │ └── ESLint.yaml └── ISSUE_TEMPLATE │ ├── REQUEST_A_FEATURE.md │ └── ISSUE_TEMPLATE.md ├── .gitignore ├── eslintrc.json ├── composer.json ├── includes ├── config.php └── classes │ ├── Database.php │ └── Chat.php ├── LICENSE.md ├── package.json ├── server.php ├── public ├── css │ └── style.css ├── js │ ├── interface.js │ ├── dom.js │ └── websockets.js └── index.php ├── database.sql ├── CHANELOG.md └── README.md /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard" 3 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | #github: johnnymast 2 | custom: "https://www.paypal.me/johnnymast" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | includes/config.php 2 | node_modules 3 | _book 4 | .idea/ 5 | composer.lock 6 | vendor/ 7 | -------------------------------------------------------------------------------- /eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "jsx": true 7 | } 8 | }, 9 | "rules": { 10 | "semi": "error" 11 | }, 12 | "env": { 13 | "browser": true 14 | }, 15 | "extends": "standard" 16 | } -------------------------------------------------------------------------------- /.github/workflows/StyleLint.yaml: -------------------------------------------------------------------------------- 1 | name: StyleLint 2 | on: [push] 3 | 4 | jobs: 5 | standardjs_job: 6 | name: On push run stylelint 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: actions-hub/stylelint@master 11 | env: 12 | PATTERN: "public/css/*.css" -------------------------------------------------------------------------------- /.github/workflows/Phpcs.yaml: -------------------------------------------------------------------------------- 1 | name: Phpcs 2 | on: [push] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | 10 | - name: PHP Code Style (phpcs) 11 | uses: chindit/actions-phpcs@master 12 | with: 13 | # Folder to check code style 14 | dir: includes/ -------------------------------------------------------------------------------- /.github/workflows/ESLint.yaml: -------------------------------------------------------------------------------- 1 | name: ESLint 2 | on: [push] 3 | 4 | jobs: 5 | standardjs_job: 6 | name: On push run eslint 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Installing dependenties 11 | uses: actions/setup-node@v1 12 | - run: npm install 13 | - run: npm run build --if-present 14 | - run: npm run eslint 15 | env: 16 | CI: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/REQUEST_A_FEATURE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001f9d0 Request a new feature" 3 | about: "Let me know what feature you want to add" 4 | --- 5 | 6 | 7 | 8 | > What feature are you missing? 9 | 10 | e.g. "I would like to see private chat's being implemented." 11 | 12 | > What solution would you like to see? 13 | 14 | e.g. "Please implement private chat." 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F37A Report a bug" 3 | about: "Oh no you ran into a bug. Please let us know." 4 | --- 5 | 6 | ### Expected behavior 7 | 8 | 9 | 10 | ### Actual behavior 11 | 12 | 13 | 14 | ### Steps to reproduce the behavior 15 | 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "johnnymast/mysql_websocket_chat", 3 | "description": "Quickly get a websocket chat up and running.", 4 | "minimum-stability": "stable", 5 | "license": "proprietary", 6 | "authors": [ 7 | { 8 | "name": "Johnny Mast", 9 | "email": "mastjohnny@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "cboden/ratchet": "v0.4.1", 14 | "fzaninotto/faker": "^1.7", 15 | "ext-pdo": "*", 16 | "squizlabs/php_codesniffer": "*", 17 | "ext-json": "*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /includes/config.php: -------------------------------------------------------------------------------- 1 | 12 | * @license https://opensource.org/licenses/MIT MIT 13 | * @version GIT:1.4 14 | * @link https://github.com/johnnymast/mysql_websocket_chat 15 | * @since GIT:1.0 16 | */ 17 | 18 | date_default_timezone_set('EUROPE/AMSTERDAM'); 19 | 20 | define('DATABASE_HOST', 'localhost'); 21 | define('DATABASE_PORT', 3306); 22 | define('DATABASE_USERNAME', 'root'); 23 | define('DATABASE_PASSWORD', 'root'); 24 | define('DATABASE_DB', 'socket_chat'); 25 | define('ENABLE_DATABASE', false); 26 | 27 | 28 | /** 29 | * The host can either be an IP or a hostname 30 | * on this machine. The port is just the port 31 | * plain and simple. 32 | */ 33 | define('WEBSOCKET_SERVER_IP', '127.0.0.1'); 34 | define('WEBSOCKET_SERVER_PORT', '8080'); 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Johnny Mast 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mysql_websocket_chat", 3 | "version": "1.0.0", 4 | "description": "Quickly get a websocket chat up and running.", 5 | "main": "index.js", 6 | "devDependencies": { 7 | "eslint-config-standard": "^14.1.1", 8 | "eslint-formatter-pretty": "^3.0.1", 9 | "eslint-plugin-import": "^2.20.2", 10 | "eslint-plugin-node": "^11.1.0", 11 | "eslint-plugin-promise": "^4.2.1", 12 | "eslint-plugin-standard": "^4.0.1", 13 | "rimraf": "^3.0.2", 14 | "standard": "^14.3.3", 15 | "stylelint": "^13.3.2", 16 | "stylelint-config-standard": "^20.0.0" 17 | }, 18 | "scripts": { 19 | "test": "echo \"Error: no test specified\" && exit 1", 20 | "eslint": "eslint -c eslintrc.json --format=pretty ./public/js" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/johnnymast/mysql_websocket_chat.git" 25 | }, 26 | "keywords": [ 27 | "websocket-chat" 28 | ], 29 | "author": "phpclasses", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/johnnymast/mysql_websocket_chat/issues" 33 | }, 34 | "homepage": "https://github.com/johnnymast/mysql_websocket_chat#readme" 35 | } 36 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | run(); 53 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | } 4 | 5 | .starter-template { 6 | padding: 40px 15px; 7 | text-align: center; 8 | } 9 | 10 | .chat_dialog { 11 | width: 70%; 12 | height: 500px; 13 | border: 1px solid black; 14 | text-align: left; 15 | padding: 5px; 16 | float: left; 17 | position: relative; 18 | } 19 | 20 | .chat_dialog .priv_msg { 21 | color: blue; 22 | font-style: italic; 23 | } 24 | 25 | .user_list { 26 | width: 20%; 27 | height: 500px !important; 28 | border: 1px solid black; 29 | text-align: left; 30 | padding: 5px; 31 | float: left; 32 | } 33 | 34 | .clear { 35 | clear: both; 36 | } 37 | 38 | .client_chat { 39 | width: 94% !important; 40 | } 41 | 42 | .input-group.client { 43 | width: 91% !important; 44 | } 45 | 46 | .connection_alert { 47 | margin-top: 15px; 48 | width: 90% !important; 49 | } 50 | 51 | .connection_alert .error_type::after { 52 | content: ':'; 53 | } 54 | 55 | .input-group-btn { 56 | float: left; 57 | } 58 | 59 | .input-group-addon.name_bit { 60 | font-weight: bold; 61 | } 62 | 63 | .client_chat.error { 64 | background-color: #fce4e4; 65 | border: 1px solid #c03; 66 | outline: none; 67 | } 68 | 69 | .client_user_you { 70 | font-weight: bold; 71 | color: red; 72 | } 73 | 74 | ul.typing_indicator { 75 | padding-left: 5px; 76 | position: absolute; 77 | bottom: 0; 78 | left: 0; 79 | } 80 | 81 | ul.typing_indicator li { 82 | padding: 0; 83 | font-style: italic; 84 | list-style-position: inside; 85 | color: #b8acab; 86 | } 87 | -------------------------------------------------------------------------------- /public/js/interface.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | document.addEventListener('DOMContentLoaded', () => { 3 | const setChatTarget = (target) => { 4 | if (!target) { 5 | target = 'Channel' 6 | } 7 | 8 | dom('.chat_target').text(target) 9 | } 10 | 11 | /*** 12 | * This is more like a confidence setup 13 | * for the interface. It does not really help 14 | * with the chat functionality. 15 | */ 16 | 17 | /** 18 | * Before we start hide the error 19 | * message. 20 | */ 21 | dom('.connection_alert').hide() 22 | 23 | dom('.client_chat').on('keyup', (evt) => { 24 | if (evt.target.value.length > 0) { 25 | registerTyping(true) 26 | } else { 27 | registerTyping(false) 28 | } 29 | }) 30 | 31 | /** 32 | * Just to make it feel like a real chat. 33 | * Send the message if enter has been pressed. 34 | */ 35 | dom('.client_chat').on('keypress', (evt) => { 36 | if (evt.key === 'Enter') { 37 | sendMessage() 38 | } 39 | }) 40 | 41 | dom('.user_list').on('change', (evt) => { 42 | const list = evt.target 43 | let to = null 44 | 45 | if (list.selectedIndex >= 0) { 46 | to = list.options[list.selectedIndex].text 47 | } 48 | 49 | setChatTarget(to) 50 | }) 51 | 52 | /** 53 | * Submit has been pressed execute sending 54 | * to server. 55 | */ 56 | dom('.btn-send.chat_btn').on('click', () => { 57 | sendMessage() 58 | registerTyping(false) 59 | }) 60 | 61 | setChatTarget() 62 | }) 63 | /* eslint-enable no-undef */ 64 | -------------------------------------------------------------------------------- /database.sql: -------------------------------------------------------------------------------- 1 | 2 | -- phpMyAdmin SQL Dump 3 | -- version 5.1.0 4 | -- https://www.phpmyadmin.net/ 5 | -- 6 | -- Host: db 7 | -- Gegenereerd op: 27 apr 2021 om 17:26 8 | -- Serverversie: 10.3.28-MariaDB-1:10.3.28+maria~focal 9 | -- PHP-versie: 7.4.16 10 | 11 | SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; 12 | START TRANSACTION; 13 | SET time_zone = "+00:00"; 14 | 15 | 16 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 17 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 18 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 19 | /*!40101 SET NAMES utf8mb4 */; 20 | 21 | -- 22 | -- Database: `socket_chat` 23 | -- 24 | CREATE DATABASE IF NOT EXISTS `socket_chat` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci; 25 | USE `socket_chat`; 26 | 27 | -- -------------------------------------------------------- 28 | 29 | -- 30 | -- Tabelstructuur voor tabel `chat_interactions` 31 | -- 32 | 33 | CREATE TABLE IF NOT EXISTS `chat_interactions` ( 34 | `message_id` int(11) NOT NULL AUTO_INCREMENT, 35 | `to_id` varchar(255) DEFAULT NULL, 36 | `from_id` varchar(255) NOT NULL, 37 | `message` text NOT NULL, 38 | `time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), 39 | `ip_address` varchar(255) NOT NULL, 40 | PRIMARY KEY (`message_id`) 41 | ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1; 42 | 43 | 44 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 45 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 46 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -------------------------------------------------------------------------------- /includes/classes/Database.php: -------------------------------------------------------------------------------- 1 | 12 | * @license https://opensource.org/licenses/MIT MIT 13 | * @link https://github.com/johnnymast/mysql_websocket_chat 14 | * @since 1.0 15 | */ 16 | 17 | /** 18 | * Class Database 19 | * 20 | * This class contains an insert function for mysql using pdo. 21 | * 22 | * @category Configuration 23 | * @package Mysql_Websocket_Chat 24 | * @author Johnny Mast 25 | * @license https://opensource.org/licenses/MIT MIT 26 | * @link https://github.com/johnnymast/mysql_websocket_chat 27 | * @since 1.0 28 | */ 29 | class Database extends \PDO 30 | { 31 | 32 | /** 33 | * Database constructor. 34 | * 35 | * @param string $username The username for the database 36 | * @param string $password The password for the database 37 | * @param string $host The hostname for the database 38 | * @param integer $port The port for the database 39 | * @param string $db The database name 40 | */ 41 | public function __construct($username = '', 42 | $password = '', 43 | $host = '', 44 | $port = 3306, 45 | $db = '' 46 | ) { 47 | $dsn = 'mysql:dbname=' . $db . ';host=' . $host . ':' . $port; 48 | parent::__construct($dsn, $username, $password); 49 | } 50 | 51 | /** 52 | * Insert a new record into the database. 53 | * 54 | * @param int $to_id The user_id of who the message is targeted towards 55 | * @param int $from_id The sending user_id 56 | * @param string $message The message being sent. 57 | * @param string $ip_address The originating IP Address. 58 | * 59 | * @return void 60 | */ 61 | public function insert( 62 | $to_id = 0, 63 | $from_id = 0, 64 | $message = '', 65 | $ip_address = '' 66 | ): void { 67 | $statement = $this->prepare( 68 | "INSERT INTO chat_interactions 69 | SET 70 | to_id = :to_id, 71 | from_id = :from_id, 72 | message = :message, 73 | ip_address = :ip_address" 74 | ); 75 | 76 | $statement->execute( 77 | [ 78 | 'to_id' => $to_id, 79 | 'from_id' => $from_id, 80 | 'message' => $message, 81 | 'ip_address' => $ip_address 82 | ] 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /CHANELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.4.1 Fixed the failing database layer 2 | 3 | - Manually reviewed the database changes. 4 | - Enabling database should now work fixing #54 thanks to @badguyp. 5 | - Fixed a bug in includes/classes/Database.php 6 | - Fixed a bug in includes/classes/Chat.php 7 | - Updated the database import 8 | 9 | ## 1.4 Helping developers to build from the project 10 | 11 | This release will be more about helping developers with useful boiler plate functions. This will assist them 12 | to create a new project from this one quickly. 13 | 14 | - Fixed some grammar issues 15 | - Cleaned up the code according to PSR1 and PSR2 16 | - Added GitHub Actions 17 | - Added checks for css via stylelint 18 | - Added javascript checks via eslint 19 | - Fixed a few typo's 20 | - Updated the layout of the private messages. 21 | - Added an indicator of who the user is typing to. The channel or a user. 22 | - Made the text of the active user bold red in client list and chat area. 23 | - Improved comments for JSDOC3 24 | - Added JSDOC development dependence. 25 | - Added more instructions to README.md. 26 | - Renamed CHAT_SERVER_HOST to WEBSOCKET_SERVER_IP for clarity. 27 | - Renamed CHAT_SERVER_PORT to WEBSOCKET_SERVER_PORT for clarity. 28 | - Moved the index.php and js files and css files to its own public folder. 29 | - Fixed a bug where dom(selector).prop(..) would not set a value 30 | - Updated the comments in dom.js for later documentation. Fixing #31 31 | - Added dom(selector).removeAttr(). Fixing #25 32 | - Removed external dependence bootstrap-theme.min.css from the project. 33 | - Scripts and Stylesheets are now relative to the directory your hosting it in. So that the project can be hosted in sub directories for example chat/index.php. Fixing #29 and #28. 34 | - Cleaned up the HTML code. fixing #30 35 | 36 | 37 | ## 1.3 Small new features 38 | 39 | This update will fix some small things to make the package more easy to use overall. 40 | It will also make the whole footprint of the package smaller. 41 | 42 | - Tested and fixed a last few bugs, then released. Fixing #12 43 | - Updated Faker to version 1.7 and Ratchet to 0.4.1. Fixing #5 44 | - Added auto reconnect (if server restarts or if the internet goes down). Fixing #6 45 | - Empty text should not be sent, now there will be a css animation for the input box. Fixing #22 46 | - Updated the comments for JSDOC3, fixing #14 47 | - Removed the dependency on jQuery because its slowly dieing. Fixing #9 48 | - Updated LICENSE.md to reflect the copyright of 2018, fixing #11 49 | - Updated the javascript code to ES2015, fixing #13 50 | - Closed issue to add .gitignore. We already had it. Closed and ignored #16 51 | - Updated CHANGELOG.md to be more detailed about bugs being fixed. Fixing #12 52 | - Added the License inside the README.md file. fixing #10 53 | 54 | 55 | ## 1.2 Small changes 56 | 57 | After doing some research on windows 10 i found one limitation. Windows does not allow the default configured 0.0.0.0 as a valid host to connect to. This update will update the default code to host on localhost (127.0.0.1) and connect to localhost as well. 58 | 59 | - CHAT_SERVER_IP is now deprecated as from now we will use a more generic name like CHAT_SERVER_HOST. 60 | - Fixing a bug that made the chat to not work on windows 10, fixing #2 61 | 62 | ## 1.1 Multi chat! 63 | 64 | - In this release i have added private chat!, Fixing #1 65 | 66 | ## 1.0 Initial Release 67 | 68 | This is the first stable release of mysql_websocket_chat. This project features basic websocket chat with configurable database setup to log chat message. 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | $faker->name /* Give the user a random name */ 12 | ]; 13 | $user['id'] = md5($user['username']); 14 | ?> 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | Websocket Chat 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 54 | 55 | 56 | 57 | 58 | 77 | 78 |
79 | 80 |
81 |

Websockets chat example

82 |

Open this url in a second browser and chat away! Depending on includes/config.php it will report 83 | to the database or not. All interactions are controlled by includes/classes/Chat.php

84 |
85 | 86 |
    87 |
    88 | 89 |
     
    90 | 91 | 99 | 100 |
    101 | 102 | 103 |  >>  104 | 105 | 106 | 107 | 108 | 109 | 110 |
    111 |
    112 | 113 |
    114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /public/js/dom.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | 3 | /** 4 | * This file provides a replacement for jquery. 5 | */ 6 | class Dom { 7 | /** 8 | * Construct with a selector that's all to it. 9 | * 10 | * @param {string} selector - The selector for the element to query. 11 | */ 12 | constructor (selector) { 13 | this.elm = document.querySelector(selector) 14 | } 15 | 16 | /** 17 | * Put display style block on the current element. 18 | */ 19 | hide () { 20 | this.elm.style.display = 'none' 21 | } 22 | 23 | /** 24 | * Put display style block on the current element. 25 | */ 26 | show () { 27 | this.elm.style.display = 'block' 28 | } 29 | 30 | /** 31 | * Return the actual dom element. 32 | * 33 | * @returns {HTMLElement | HTMLSelectElement | HTMLLegendElement | HTMLTableCaptionElement | HTMLTextAreaElement | HTMLModElement | HTMLHRElement | HTMLOutputElement | HTMLPreElement | HTMLEmbedElement | HTMLCanvasElement | HTMLFrameSetElement | HTMLMarqueeElement | HTMLScriptElement | HTMLInputElement | HTMLUnknownElement | HTMLMetaElement | HTMLStyleElement | HTMLObjectElement | HTMLTemplateElement | HTMLBRElement | HTMLAudioElement | HTMLIFrameElement | HTMLMapElement | HTMLTableElement | HTMLAnchorElement | HTMLMenuElement | HTMLPictureElement | HTMLParagraphElement | HTMLTableDataCellElement | HTMLTableSectionElement | HTMLQuoteElement | HTMLTableHeaderCellElement | HTMLProgressElement | HTMLLIElement | HTMLTableRowElement | HTMLFontElement | HTMLSpanElement | HTMLTableColElement | HTMLOptGroupElement | HTMLDataElement | HTMLDListElement | HTMLFieldSetElement | HTMLSourceElement | HTMLBodyElement | HTMLDirectoryElement | HTMLDivElement | HTMLUListElement | HTMLHtmlElement | HTMLAreaElement | HTMLMeterElement | HTMLAppletElement | HTMLFrameElement | HTMLOptionElement | HTMLImageElement | HTMLLinkElement | HTMLHeadingElement | HTMLSlotElement | HTMLVideoElement | HTMLBaseFontElement | HTMLTitleElement | HTMLButtonElement | HTMLHeadElement | HTMLParamElement | HTMLTrackElement | HTMLOListElement | HTMLDataListElement | HTMLLabelElement | HTMLFormElement | HTMLTimeElement | HTMLBaseElement | null | *} 34 | */ 35 | get () { 36 | return this.elm 37 | } 38 | 39 | /** 40 | * Get or set a property on a dom element. Don't pass 41 | * the value parameter if you want to get the value. 42 | * If you want to set the value pass the value. 43 | * 44 | * @param {string} attribute - The attribute name 45 | * @param {string}[value = ''] - Value to set (optional) 46 | * @returns {string | null} 47 | */ 48 | prop (attribute, value) { 49 | if (typeof value !== 'undefined') { 50 | this.elm.setAttribute(attribute, value) 51 | } 52 | return this.elm.getAttribute(attribute) 53 | } 54 | 55 | /** 56 | * Remove an attribute from on a dom element. 57 | * 58 | * @param {string} attribute - The attribute to remove. 59 | */ 60 | removeAttr (attribute) { 61 | this.elm.removeAttribute(attribute) 62 | } 63 | 64 | /** 65 | * Append a html string to the end if the element. 66 | * 67 | * @param {string} html - Html string to append to the element 68 | */ 69 | append (html) { 70 | this.elm.insertAdjacentHTML('beforeend', html) 71 | } 72 | 73 | /** 74 | * Get the value of element. If value is passed the value will be set 75 | * to this value instead. 76 | * 77 | * @param {string} [value=null] value - If set this value will be set on the element. (optional) 78 | * @returns {string | number} 79 | */ 80 | val (value) { 81 | if (typeof value !== 'undefined') { 82 | this.elm.value = value 83 | } 84 | return this.elm.value 85 | } 86 | 87 | /** 88 | * Set the text value of the element. 89 | * 90 | * @param {string} [value] value - The text to set as innerText for the element. 91 | * @returns {string | number} 92 | */ 93 | text (value = '') { 94 | if (typeof value !== 'undefined' && value.length > 0) { 95 | this.elm.innerText = value 96 | } 97 | return this.elm.innerText 98 | } 99 | 100 | /** 101 | * Add a class to the given element 102 | * 103 | * @param {string} className - Classname to add. 104 | */ 105 | addClass (className) { 106 | this.elm.className += ' ' + className 107 | } 108 | 109 | /** 110 | * Remove a class from the given element 111 | * 112 | * @param {string} className - Classname to remove. 113 | */ 114 | removeClass (className) { 115 | let elClass = ' ' + this.elm.className + ' ' 116 | while (elClass.indexOf(' ' + className + ' ') !== -1) { 117 | elClass = elClass.replace(' ' + className + ' ', '') 118 | } 119 | this.elm.className = elClass 120 | } 121 | 122 | /** 123 | * Ad a EventListener for the dom element 124 | * 125 | * @param {string} event - The event name. 126 | * @param callback 127 | */ 128 | on (event, callback) { 129 | this.elm.addEventListener(event, callback, false) 130 | } 131 | } 132 | 133 | /** 134 | * Return a new instance of _dom (the jquery clone) so the code could 135 | * 136 | * @param {string} selector - The selector for the dom element. 137 | * @returns {Dom} 138 | */ 139 | // eslint-disable-next-line no-unused-vars 140 | const dom = (selector) => { 141 | return new Dom(selector) 142 | } 143 | /* eslint-enable no-undef */ 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Code Triagers Badge](https://www.codetriage.com/johnnymast/mysql_websocket_chat/badges/users.svg)](https://www.codetriage.com/johnnymast/mysql_websocket_chat) 2 | ![ESLint](https://github.com/johnnymast/mysql_websocket_chat/workflows/ESLint/badge.svg) 3 | ![StyleLint](https://github.com/johnnymast/mysql_websocket_chat/workflows/StyleLint/badge.svg) 4 | ![Phpcs](https://github.com/johnnymast/mysql_websocket_chat/workflows/Phpcs/badge.svg) 5 | 6 | # MYSQL WEBSOCKET CHAT 7 | 8 | Welcome to this Hackaton project I created for user hrushi on [phpclasses.org](http://www.phpclasses.org/recommend/754-I-need-to-create-realtime-user-to-user-chat.html). The idea was to create a web socket chat application 9 | that could be logging to a database. So here is what you need to get this up and running. ***Please note*** the minimum required PHP version is 7.0- this is not because it wanted this but it is because of the dependencies this project has. 10 | 11 | 12 | # Step 1: install composer 13 | 14 | First thing you is installing composer on to your system. You can get composer [here](https://getcomposer.org/download/). Don't worry it might seem intimidating but it is not. 15 | 16 | # Step 2: Install the project 17 | 18 | ## Using composer 19 | 20 | Installing the project using composer is hands down the easiest way to get started. This method will download the project from GitHub 21 | and automatically install its dependencies for you. Presuming you installed composer (step 1) execute the following commands on the command-line. 22 | 23 | ```bash 24 | $ composer create-project johnnymast/mysql_websocket_chat chat 25 | $ cd chat 26 | ``` 27 | 28 | In the above example I am using a mac so my prompt will display different then you if you are on windows. 29 | 30 | ## Downloaded from phpclasses.org 31 | 32 | If you download this package in a zip file from [phpclasses.org](http://www.phpclasses.org/package/9947-PHP-Websocket-starter-project.html) you will have to extract the zip package to a location of your liking. Then 33 | change directory into that directory and execute the following command on your prompt. 34 | 35 | ```bash 36 | $ composer install 37 | ``` 38 | 39 | In the above example I am using a mac so my prompt will display different then you if you are on windows. 40 | 41 | 42 | # Step 3: Configure the server 43 | 44 | ## Websocket configuration 45 | 46 | This project can be split into two different components. The WebSocket server is the server.php in the root directory. The second part 47 | is the frontend part located in public/index.php. For the WebSocket server, there are two configuration options that you can configure in includes/config.php. 48 | 49 | #### WEBSOCKET_SERVER_IP 50 | 51 | This flag allows you to configure the WebSocket server's IP-address. By default the value 127.0.0.1 has been set. 52 | 53 | #### WEBSOCKET_SERVER_PORT 54 | 55 | This will configure what port the WebSocket server will listen on. The default value has been set to 8080. You can change this 56 | value if it clashes with other services running on your machine. 57 | 58 | 59 | 60 | ## Database configuration 61 | 62 | This server can run either with or without a database. By default i have disabled the use of a database server (ENABLE_DATABASE) but you can enable it by switching the ENABLE_DATABASE to true 63 | in the [includes/config.php](https://github.com/johnnymast/mysql_websocket_chat/blob/master/includes/config.php) file. 64 | 65 | | Flag | Description | 66 | | --- | --- | 67 | | DATABASE_HOST | The database username goes in here. By default this has been set to root. | 68 | | DATABASE_PORT | The database port goes in here. By default this has been set to 3306. | 69 | | DATABASE_USERNAME | The database username goes in here. By default this has been set to root.| 70 | | DATABASE_PASSWORD | Enter the password to access the database there. By default this has been set to root.| 71 | | DATABASE_DB | Enter the name of the database here. By default this has been set to socket_chat.| 72 | | ENABLE_DATABASE | This flag will turn using the database on or off by setting its value to true or false.| 73 | 74 | 75 | ***Please note*** if you enable the database make sure you update the credentials as well (see table above). Also, if you enable the database make sure you have imported [database.sql](https://github.com/johnnymast/mysql_websocket_chat/blob/master/database.sql) into your database. 76 | 77 | 78 | # Step 4: Fire up the WebSocket server 79 | 80 | Change direction into the chat directory and fire up the server. 81 | 82 | ```bash 83 | $ cd chat 84 | $ php ./server.php 85 | ``` 86 | 87 | When you see no output and the command seems to hang that's when you know its running. 88 | 89 | 90 | # Step 5: Point a web service to the public directory 91 | 92 | In the chat directory, you will find index.php. This file will be the client for your chat application. Make sure you set any web service its document root to the public/ folder. Alternatively, if you don't have access to a webserver you can also try using PHP's 93 | build-in webserver. 94 | 95 | ```bash 96 | $ cd public 97 | $ php -S 127.0.0.1:8000 98 | ``` 99 | 100 | This will start an webserver on port 8000 101 | 102 | # Step 6: Chat away! 103 | 104 | Now open up 2 chat tabs and point them to localhost (or maybe a virtual host you configured) and chat away with your self. 105 | 106 | 107 | ## Functionality 108 | 109 | #### Private chats 110 | 111 | If you want to test private chats you can single click any user in the user list on the right of the screen. Then type your message 112 | in the message bar, this will send a private message only to that user. 113 | 114 | 115 | ## Changes 116 | 117 | If you wish to know what has changed in this version of Mysql WebSocket Chat you can always checkout the changelog [here](https://github.com/johnnymast/mysql_websocket_chat/blob/master/CHANELOG.md). 118 | 119 | 120 | ## Author 121 | 122 | This package is created and maintained by [Johnny Mast](mailto:mastjohnny@gmail.com). For feature requests or suggestions you could consider sending me an e-mail. 123 | 124 | ## Enjoy 125 | 126 | Oh and if you've come down this far, you might as well [follow me](https://twitter.com/mastjohnny) on twitter. 127 | 128 | 129 | ## License 130 | 131 | MIT License 132 | 133 | Copyright (c) 2021 Johnny Mast 134 | 135 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 136 | 137 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 138 | 139 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 140 | 141 | -------------------------------------------------------------------------------- /includes/classes/Chat.php: -------------------------------------------------------------------------------- 1 | 12 | * @license https://opensource.org/licenses/MIT MIT 13 | * @link https://github.com/johnnymast/mysql_websocket_chat 14 | * @since GIT:1.0 15 | */ 16 | 17 | use Ratchet\ConnectionInterface; 18 | use Ratchet\MessageComponentInterface; 19 | 20 | /** 21 | * Class Chat 22 | * 23 | * The main Chat controller for mysql_websocket_chat 24 | * 25 | * PHP version 7.2 and up. 26 | * Chat 27 | * 28 | * @category Configuration 29 | * @package Mysql_Websocket_Chat 30 | * @author Johnny Mast 31 | * @license https://opensource.org/licenses/MIT MIT 32 | * @link https://github.com/johnnymast/mysql_websocket_chat 33 | * @since 1.0 34 | */ 35 | class Chat implements MessageComponentInterface 36 | { 37 | /** 38 | * This member keeps track of all 39 | * connected clients. 40 | * 41 | * @var \SplObjectStorage 42 | */ 43 | protected $clients = null; 44 | 45 | /** 46 | * This member keeps track of all 47 | * connected users. 48 | * 49 | * @var array 50 | */ 51 | protected $users = []; 52 | 53 | /** 54 | * Instance of the database class. 55 | * 56 | * @var Database 57 | */ 58 | protected $db = null; 59 | 60 | /** 61 | * Chat constructor. 62 | * 63 | * @param Database $db Instance of Database 64 | */ 65 | public function __construct($db) 66 | { 67 | $this->clients = new SplObjectStorage; 68 | $this->db = $db; 69 | } 70 | 71 | /** 72 | * If a new connection has been opened this function will be called. 73 | * 74 | * @param ConnectionInterface $conn The unique connection identifier. 75 | * 76 | * @return void 77 | */ 78 | public function onOpen(ConnectionInterface $conn): void 79 | { 80 | $this->clients->attach($conn); 81 | } 82 | 83 | /** 84 | * If any clients sends a message it will be passed trough here. 85 | * 86 | * @param ConnectionInterface $from Reference to the unique client 87 | * @param string $msg The message being sent 88 | * 89 | * @return void 90 | * @throws \Exception 91 | */ 92 | public function onMessage(ConnectionInterface $from, $msg): void 93 | { 94 | foreach ($this->clients as $client) { 95 | $package = json_decode($msg); 96 | 97 | if (is_object($package) === true) { 98 | 99 | /** 100 | * We need to switch the message type because in the future 101 | * this could be a message or maybe a request for all chatters 102 | * in the chat. For now we only use the message type but we can 103 | * build on that later. 104 | */ 105 | switch ($package->type) { 106 | case 'message': 107 | if ($from !== $client) { 108 | if (empty($package->to_user) == false && isset($package->to_user->id) == true) { 109 | 110 | /** 111 | * Find the client to send the message to 112 | */ 113 | foreach ($this->users as $resourceId => $user) { 114 | 115 | /** 116 | * Non target users will not see this message 117 | * on their screens. 118 | */ 119 | if ($user['user']->id === $package->to_user->id) { 120 | 121 | /** 122 | * Defined in includes/config.php 123 | */ 124 | if (ENABLE_DATABASE == true) { 125 | if (isset($package->user) 126 | && is_object($package->user) == true 127 | ) { 128 | /** 129 | * Insert private chat 130 | */ 131 | $this->db->insert( 132 | $package->to_user->id, 133 | $package->user->id, 134 | $package->message, 135 | $client->remoteAddress 136 | ); 137 | } 138 | } 139 | 140 | $targetClient = $user['client']; 141 | $targetClient->send($msg); 142 | return; 143 | } 144 | } 145 | } else { 146 | 147 | 148 | /** 149 | * Defined in includes/config.php 150 | */ 151 | if (ENABLE_DATABASE == true) { 152 | if (isset($package->user) 153 | and is_object($package->user) == true 154 | ) { 155 | /** 156 | * Insert channel chat 157 | */ 158 | $this->db->insert( 159 | null, 160 | $package->user->id, 161 | $package->message, 162 | $client->remoteAddress 163 | ); 164 | } 165 | } 166 | $client->send($msg); 167 | } 168 | } 169 | break; 170 | case 'registration': 171 | $this->users[$from->resourceId] = [ 172 | 'user' => $package->user, 173 | 'client' => $from 174 | ]; 175 | break; 176 | case 'userlist': 177 | $list = []; 178 | foreach ($this->users as $resourceId => $value) { 179 | $list[] = $value['user']; 180 | } 181 | $new_package = [ 182 | 'users' => $list, 183 | 'type' => 'userlist' 184 | ]; 185 | $new_package = json_encode($new_package); 186 | $client->send($new_package); 187 | break; 188 | 189 | case 'typing': 190 | if ($from != $client) { 191 | if (empty($package->user) == false) { 192 | /** 193 | * Find the client to send the message to 194 | */ 195 | foreach ($this->users as $resourceId => $user) { 196 | if ($resourceId == $from->resourceId) { 197 | continue; 198 | } 199 | 200 | $new_package = [ 201 | 'user' => $package->user, 202 | 'type' => 'typing', 203 | 'value' => $package->value, 204 | ]; 205 | 206 | $targetClient = $user['client']; 207 | $targetClient->send($msg); 208 | } 209 | } 210 | } 211 | break; 212 | default: 213 | throw new \Exception('Unexpected value'); 214 | break; 215 | } 216 | } 217 | } 218 | } 219 | 220 | /** 221 | * The onclose callback. 222 | * 223 | * @param ConnectionInterface $conn The unique connection identifier. 224 | * 225 | * @return void 226 | */ 227 | public function onClose(ConnectionInterface $conn): void 228 | { 229 | unset($this->users[$conn->resourceId]); 230 | $this->clients->detach($conn); 231 | } 232 | 233 | /** 234 | * The onError callback. Will be called on you guessed it, an error :) 235 | * 236 | * @param ConnectionInterface $conn The unique connection identifier. 237 | * @param \Exception $e The raised exception 238 | * 239 | * @return void 240 | */ 241 | public function onError(ConnectionInterface $conn, \Exception $e): void 242 | { 243 | unset($this->users[$conn->resourceId]); 244 | $conn->close(); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /public/js/websockets.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | /** 3 | * For more information about the protocol you can read to protocol information 4 | * document at our wiki. 5 | * 6 | * @see {@link https://github.com/johnnymast/mysql_websocket_chat/wiki/Protocol|GitHub} 7 | */ 8 | 9 | const RECONNECT_IN_SEC = 10 10 | const ws = { 11 | /** 12 | * Start the connection 13 | * @type {WebSocket} 14 | */ 15 | conn: null 16 | } 17 | 18 | /** 19 | * Handle automatically reconnecting to a websocket server 20 | * if the connection was interrupted. 21 | * 22 | * @param {function} callback - The callback called upon reconnection 23 | */ 24 | WebSocket.prototype.reconnect = (callback) => { 25 | if (this.readyState === WebSocket.OPEN || this.readyState !== WebSocket.CONNECTING) { 26 | this.close() 27 | } 28 | 29 | let seconds = RECONNECT_IN_SEC 30 | const container = dom('.connection_alert .error_reconnect_countdown') 31 | const countHandle = setInterval(() => { 32 | if (--seconds <= 0) { 33 | clearInterval(countHandle) 34 | callback() 35 | return 36 | } 37 | container.text(seconds.toString()) 38 | }, 1000) 39 | } 40 | 41 | /** 42 | * Connect to the websocket server. 43 | */ 44 | const connect = () => { 45 | if (ws.conn) { 46 | if (ws.conn.readyState === WebSocket.OPEN || ws.conn.readyState === WebSocket.CONNECTING) { 47 | ws.conn.close() 48 | } 49 | delete ws.conn 50 | } 51 | 52 | ws.conn = new WebSocket('ws://' + socketHost + ':' + socketPort) 53 | 54 | /** 55 | * Connection has been established 56 | * 57 | * @param {Event} event - The onopen event. 58 | */ 59 | ws.conn.onopen = (event) => { 60 | dom('.client_chat').removeAttr('disabled') 61 | dom('.connection_alert').hide() 62 | 63 | /** 64 | * Register te client to the 65 | * server. This allows the server 66 | * to return a list of chat clients 67 | * to list on the side. 68 | */ 69 | registerClient() 70 | 71 | /** 72 | * Request the user list from 73 | * the server. If the server replies the user list 74 | * will be populated. 75 | */ 76 | requestUserlist() 77 | } 78 | 79 | /** 80 | * A new message (read package) has been received. 81 | * 82 | * @param {Event} event - The onmessage event. 83 | */ 84 | ws.conn.onmessage = (event) => { 85 | const pkg = JSON.parse(event.data) 86 | 87 | if (pkg.type === 'message') { 88 | dialogOutput(pkg) 89 | } else if (pkg.type === 'userlist') { 90 | usersOutput(pkg.users) 91 | } else if (pkg.type === 'typing') { 92 | typingOutput(pkg) 93 | } 94 | } 95 | 96 | /** 97 | * Notify the user that the connection is closed 98 | * and disable the chat bar. 99 | * 100 | * @param {Event} event - The onclose event. 101 | */ 102 | ws.conn.onclose = (event) => { 103 | console.log('Connection closed!') 104 | 105 | dom('.client_chat').prop('disabled', true) 106 | dom('.connection_alert').show() 107 | clearUserlist() 108 | 109 | if (event.target.readyState === WebSocket.CLOSING || event.target.readyState === WebSocket.CLOSED) { 110 | event.target.reconnect(connect) 111 | } 112 | } 113 | 114 | /** 115 | * Display a message in the terminal if 116 | * we run into an error. 117 | * 118 | * @param {Event} event - The error event. 119 | */ 120 | ws.conn.onerror = (event) => { 121 | console.log('We have received an error!', event) 122 | } 123 | } 124 | 125 | /** 126 | * Remove all users from the users on the 127 | * side of the screen. 128 | */ 129 | clearUserlist = () => { 130 | /** 131 | * First of all clear the current userlist 132 | */ 133 | while (userList.firstChild) { 134 | userList.removeChild(userList.firstChild) 135 | } 136 | } 137 | 138 | /** 139 | * Put a package (message) on the screen 140 | * for you or others to read. 141 | * 142 | * @param {object} pkg - The package object to display. 143 | */ 144 | dialogOutput = (pkg) => { 145 | if (pkg.to_user) { 146 | if (pkg.to_user.id === chat_user.id) { 147 | dom('.chat_dialog').append('(Private from << ' + pkg.user.username + ') ' + pkg.message + '
    ') 148 | } else { 149 | dom('.chat_dialog').append('(Private to >> ' + pkg.to_user.username + '): ' + pkg.message + '
    ') 150 | } 151 | } else { 152 | dom('.chat_dialog').append('' + pkg.user.username + ': ' + pkg.message + '
    ') 153 | } 154 | } 155 | 156 | /** 157 | * Update the user list in the UI 158 | * 159 | * @param {array} users - Array of uses to display in the chatroom. 160 | */ 161 | usersOutput = (users) => { 162 | /** 163 | * First get the current select value 164 | * on the list. This is so we can restore 165 | * the selected list item after requesting 166 | * fow new users. 167 | */ 168 | const selectedUser = userList.value 169 | 170 | /** 171 | * Before we start adding users 172 | * to the userlist make sure we erase 173 | * all the old users of the screen. 174 | */ 175 | clearUserlist() 176 | 177 | for (const index in users) { 178 | if (typeof users[index] !== 'undefined') { 179 | const user = users[index] 180 | const elm = document.createElement('OPTION') 181 | 182 | elm.value = user.id 183 | elm.appendChild(document.createTextNode(user.username)) 184 | 185 | if (elm.value === chat_user.id) { 186 | elm.classList = ['client_user_you'] 187 | elm.disabled = 'disabled' 188 | } 189 | 190 | if (selectedUser.length > 0 && elm.value === selectedUser) { 191 | elm.selected = 'selected' 192 | } 193 | 194 | userList.appendChild(elm) 195 | } 196 | } 197 | } 198 | 199 | /** 200 | * Display a message on the screen indicating some is typing a message. 201 | * 202 | * @param {object} pkg - The received package from the server 203 | */ 204 | typingOutput = (pkg) => { 205 | if (typeof pkg === 'object') { 206 | const user = pkg.user 207 | const isTyping = pkg.value 208 | 209 | const indicator = dom('.typing_indicator').get() 210 | const typingMessage = dom(`.typing_indicator li[data-userid="${user.id}"]`).get() 211 | 212 | if (typingMessage) { 213 | typingMessage.parentNode.removeChild(typingMessage) 214 | } 215 | 216 | if (isTyping) { 217 | const msg = `${user.username} is typing a message` 218 | const li = document.createElement('LI') 219 | li.dataset.userid = user.id 220 | li.innerText = msg 221 | 222 | indicator.appendChild(li) 223 | } 224 | } 225 | } 226 | 227 | /** 228 | * We need to register this browser window (client) 229 | * to the server. We do this so we can sent private 230 | * messages to other users. 231 | */ 232 | registerClient = () => { 233 | /** 234 | * Create a registration package to send to the 235 | * server. 236 | */ 237 | let pkg = { 238 | user: chat_user, /* Defined in index.php */ 239 | type: 'registration' 240 | } 241 | 242 | pkg = JSON.stringify(pkg) 243 | 244 | /** 245 | * Send the package to the server 246 | */ 247 | if (ws.conn && ws.conn.readyState === WebSocket.OPEN) { 248 | ws.conn.send(pkg) 249 | } 250 | } 251 | 252 | /** 253 | * Request a list of current active 254 | * chat users. We do this every x seconds 255 | * so we can update the ui. 256 | */ 257 | requestUserlist = () => { 258 | setInterval(() => { 259 | if (ws.conn.readyState !== WebSocket.CLOSING && ws.conn.readyState !== WebSocket.CLOSED) { 260 | /** 261 | * Create a package to request the list of users 262 | */ 263 | let pkg = { 264 | user: chat_user, /* Defined in index.php */ 265 | type: 'userlist' 266 | } 267 | 268 | /** 269 | * We need a object copy of package 270 | * to send to dialog_output() but we 271 | * also want to turn the original package 272 | * into a string so we can send it over the 273 | * socket to the server. 274 | * 275 | * @type {Object} 276 | */ 277 | pkg = JSON.stringify(pkg) 278 | if (ws.conn && ws.conn.readyState === WebSocket.OPEN) { 279 | ws.conn.send(pkg) 280 | } 281 | } 282 | }, 2000) 283 | } 284 | 285 | /** 286 | * Register user is typing yes or no. 287 | * 288 | * @param {boolean} currently - Is this user currently typing? 289 | */ 290 | registerTyping = (currently) => { 291 | /** 292 | * Create a package to send to the 293 | * server. 294 | */ 295 | let pkg = { 296 | user: chat_user, /* Defined in index.php */ 297 | type: 'typing', 298 | value: currently || false 299 | } 300 | 301 | pkg = JSON.stringify(pkg) 302 | 303 | /** 304 | * Send the package to the server 305 | */ 306 | if (ws.conn && ws.conn.readyState === WebSocket.OPEN) { 307 | ws.conn.send(pkg) 308 | } 309 | } 310 | 311 | /** 312 | * Send a chat message to the server 313 | */ 314 | sendMessage = () => { 315 | /** 316 | * Catch the chat text 317 | * 318 | * @type {string} 319 | */ 320 | const chatMessage = dom('.client_chat').val() 321 | 322 | if (typeof chatMessage === 'undefined' || chatMessage.length === 0) { 323 | dom('.client_chat ').addClass('error') 324 | setTimeout(() => { 325 | dom('.client_chat ').removeClass('error') 326 | }, 500) 327 | } 328 | 329 | registerTyping(false) 330 | 331 | /** 332 | * When to_user is empty the 333 | * message will be sent to all users 334 | * in the chat room. 335 | * 336 | * @type {Object} 337 | */ 338 | let toUser = null 339 | 340 | /** 341 | * If a user is selected in the 342 | * userlist this will mean send messages 343 | * to that user. 344 | */ 345 | if (userList.value) { 346 | toUser = { 347 | id: userList.value, 348 | username: userList.options[userList.selectedIndex].text 349 | } 350 | } 351 | 352 | /** 353 | * Create a package to send to the 354 | * server. 355 | */ 356 | let pkg = { 357 | user: chat_user, /* Defined in index.php */ 358 | message: chatMessage, 359 | to_user: toUser, 360 | type: 'message' 361 | } 362 | 363 | /** 364 | * We need a object copy of package 365 | * to send to dialog_output() but we 366 | * also want to turn the original package 367 | * into a string so we can send it over the 368 | * socket to the server. 369 | * 370 | * @type {Object} 371 | */ 372 | const pkgObject = pkg 373 | pkg = JSON.stringify(pkg) 374 | 375 | /** 376 | * Send the package to the server 377 | */ 378 | if (ws.conn && ws.conn.readyState === WebSocket.OPEN) { 379 | ws.conn.send(pkg) 380 | } 381 | 382 | /** 383 | * Display the message we just wrote 384 | * to the screen. 385 | */ 386 | dialogOutput(pkgObject) 387 | 388 | /** 389 | * Empty the chat input bar 390 | * we don't need it anymore. 391 | */ 392 | dom('.client_chat').val('') 393 | } 394 | 395 | const userList = dom('.user_list').get() 396 | 397 | document.addEventListener('DOMContentLoaded', connect) 398 | /* eslint-enable no-undef */ 399 | --------------------------------------------------------------------------------