Skip to content

ollyxar/websockets-chat

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Laravel WebSocket chat server

Version Downloads License

logo

Requirements

  • Unix (extension pcntl_fork)
  • PHP 7.1+
  • Laravel 5
  • composer

Installing WebSockets Chat

The recommended way to install WebSockets is through Composer.

# Install Composer
curl -sS https://getcomposer.org/installer | php

Next, run the Composer command to install the latest stable version of WebSockets:

php composer.phar require ollyxar/websockets-chat

After updating composer, add the service provider to the providers array in config/app.php

Ollyxar\WSChat\WSChatServiceProvider::class,

Configuration

You can customize variables bellow by adding config-file: websockets-chat.php in the config folder:

parameter description example
handler Handler Class (extends of Worker) \App\MyHandler
host Host (ip) 0.0.0.0
port Port 2083
worker_count Count of forked process 4
use_ssl Used protocol false
cert PEM certificate /etc/nginx/conf.d/wss.pem
pass_phrase PEM certificate pass phrase secret$#%

Extended Handler class

This is example how to use Handler with User authentication. If you have default configuration and file-session-storage you can use this example.

First you have to install auth-helper:

php composer.phar require ollyxar/laravel-auth

Then create your Handler.php:

namespace App;

use Generator;
use Ollyxar\LaravelAuth\FileAuth;
// or you can use RedisAuth if you're storing sessions in the Redis-server:
// use Ollyxar\LaravelAuth\RedisAuth;
use Ollyxar\WebSockets\{
   Frame,
   Handler as Worker,
   Dispatcher
};

/**
 * Class Handler
 * @package App
 */
class Handler extends Worker
{
    /**
     * Connected users
     *
     * @var array
     */
    protected $users = [];

    /**
     * Append connected user
     *
     * @param array $headers
     * @param $socket
     * @return bool
     */
    private function fillUser(array $headers, $socket): bool
    {
        if ($userId = FileAuth::getUserIdByHeaders($headers)) {
            // allow only one connection for worker per user
            if (!in_array($userId, $this->users)) {
                $this->users[(int)$socket] = $userId;
                return true;
            }
        }

        return false;
    }

    /**
     * @param $client
     * @return Generator
     */
    protected function onConnect($client): Generator
    {
        $userName = User::where('id', (int)$this->users[(int)$client])->first()->name;
        yield Dispatcher::async($this->broadcast(Frame::encode(json_encode([
            'type'    => 'system',
            'message' => $userName . ' connected.'
        ]))));
    }

    /**
     * @param array $headers
     * @param $socket
     * @return bool
     */
    protected function validateClient(array $headers, $socket): bool
    {
        return $this->fillUser($headers, $socket);
    }

    /**
     * @param $clientNumber
     * @return Generator
     */
    protected function onClose($clientNumber): Generator
    {
        $user = User::where('id', (int)@$this->users[$clientNumber])->first();
        $userName = data_get($user, 'name', '[GUEST]');

        yield Dispatcher::async($this->broadcast(Frame::encode(json_encode([
            'type'    => 'system',
            'message' => $userName . " disconnected."
        ]))));

        unset($this->users[$clientNumber]);
        yield;
    }

    /**
     * @param string $message
     * @param int $socketId
     * @return Generator
     */
    protected function onClientMessage(string $message, int $socketId): Generator
    {
        $message = json_decode($message);
        $userName = User::where('id', (int)$this->users[$socketId])->first()->name;
        $userMessage = $message->message;

        $response = Frame::encode(json_encode([
            'type'    => 'usermsg',
            'name'    => $userName,
            'message' => $userMessage
        ]));

        yield Dispatcher::async($this->broadcast($response));
    }
}

Then add markup to the front:

<div class="chat-wrapper">
    <div class="message-box" id="message-box"></div>
    <div class="panel">
        <input type="text" name="message" id="message" placeholder="Message"/>
        <button id="send-btn" class="button">Send</button>
    </div>
</div>

And JS code:

var wsUri = "ws://laravel5.dev:2083",
    ws = new WebSocket(wsUri);

ws.onopen = function () {
    var el = document.createElement('div');
    el.classList.add('system-msg');
    el.innerText = 'Connection established';
    document.getElementById('message-box').appendChild(el);
};

document.getElementById('message').addEventListener('keydown', function (e) {
    if (e.keyCode === 13) {
        document.getElementById('send-btn').click();
    }
});

document.getElementById('send-btn').addEventListener('click', function () {
    var mymessage = document.getElementById('message').value;

    if (mymessage === '') {
        alert("Enter Some message Please!");
        return;
    }

    var objDiv = document.getElementById("message-box");
    objDiv.scrollTop = objDiv.scrollHeight;

    var msg = {
        message: mymessage
    };
    ws.send(JSON.stringify(msg));
});

ws.onmessage = function (ev) {
    var msg = JSON.parse(ev.data),
        type = msg.type,
        umsg = msg.message,
        uname = msg.name;

    var el = document.createElement('div');

    if (type === 'usermsg') {
        el.innerHTML = '<span class="user-name">' + uname + '</span> : <span class="user-message">' + umsg + '</span>';
        document.getElementById('message-box').appendChild(el);
    }
    if (type === 'system') {
        el.classList.add('system-msg');
        el.innerText = umsg;
        document.getElementById('message-box').appendChild(el);
    }

    document.getElementById('message').value = '';

    var objDiv = document.getElementById('message-box');
    objDiv.scrollTop = objDiv.scrollHeight;
};

ws.onerror = function (e) {
    var el = document.createElement('div');
    el.classList.add('system-error');
    el.innerText = 'Error Occurred - ' + e.data;
    document.getElementById('message-box').appendChild(el);
};
ws.onclose = function () {
    var el = document.createElement('div');
    el.classList.add('system-msg');
    el.innerText = 'Connection Closed';
    document.getElementById('message-box').appendChild(el);
};

Starting WebSocket Server

php artisan websockets-chat:run

Sending direct message to the server

php artisan websockets-chat:send "Hello from system!"