Socket in Laravel with Reverb

Socket in Laravel with Reverb

Install Reverb in laravel

Install laravel echo in frontend with PusherJS

Update env

BROADCAST_DRIVER=reverb

REVERB_APP_ID=22..88
REVERB_APP_KEY=eql...x52m
REVERB_APP_SECRET=v3kdi...76dc
REVERB_HOST="sohag.pro"
REVERB_PORT=443
REVERB_SCHEME=https

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

Create Event

with ShouldBroadcast

<?php

namespace App\Events;

use App\Models\Car;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class TestEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     */
    public function __construct(public Car $car)
    {
        //
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new Channel('car.' . $this->car->id),
        ];
    }
}

Fire event


$car = Car::find(7);
TestEvent::dispatch($car);

Setup Frontend

in resources/js/bootstrap.js

import Echo from 'laravel-echo';

import Pusher from 'pusher-js';
window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'reverb',
    key: import.meta.env.VITE_REVERB_APP_KEY,
    wsHost: import.meta.env.VITE_REVERB_HOST,
    wsPort: import.meta.env.VITE_REVERB_PORT,
    wssPort: import.meta.env.VITE_REVERB_PORT,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});

Receive Event in React

const [isSubscribed, setIsSubscribed] = useState(false);

useEffect(() => {
    if (!isSubscribed && car && car.id) {
        Echo.channel(`car.${car.id}`).listen("TestEvent", (e) => {
            console.log(e.car);
            setCar(e.car);
            setLatestBid(e.car.highest_bid.amount);
            toast.success("New Bid placed of $" + parseInt(e.car.highest_bid.amount));
        });
        setIsSubscribed(true); // Set subscribed flag to true
    }

    // Cleanup subscription on component unmount
    return () => {
        if (isSubscribed && car && car.id) {
            Echo.leave(`car.${car.id}`);
            setIsSubscribed(false); // Reset subscribed flag
        }
    };
}, [isSubscribed]);

Receive Event in Vanilla Js

const receiverName = '{{ $receiver->name }}';
const senderName = '{{ auth()->user()->name }}';
const receiverId = {{ $receiver->id }};
const senderId = {{ auth()->id() }};
const messagesDiv = document.getElementById('messages');
window.addEventListener('load', () => {
    window.Echo.private(`chat.{{ auth()->id() }}.{{ $receiver->id }}`)
        .listen('TestEvent', (e) => {
            console.log(e);
            let pushMessage = e.message.message;
            let image = e.message.image;
            let time = e.message.created_at;
            // format as 12 hour time format with date and time
            time = new Date(time).toLocaleString('en-US', {
                timeZone: '{{ config('app.timezone') }}',
                hour: 'numeric',
                minute: 'numeric',
                hour12: true,
                month: 'short',
                day: 'numeric'
            });
            document.getElementById('messages').innerHTML += generateBlock(pushMessage, image, receiverName,
                time);

            // messages div scroll to bottom
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        });
    window.Echo.private(`chat.{{ $receiver->id }}.{{ auth()->id() }}`)
        .listen('TestEvent', (e) => {
            console.log(e);
            let pushMessage = e.message.message;
            let image = e.message.image;
            let time = e.message.created_at;
            // format as 12 hour time format with date and time
            time = new Date(time).toLocaleString('en-US', {
                timeZone: '{{ config('app.timezone') }}',
                hour: 'numeric',
                minute: 'numeric',
                hour12: true,
                month: 'short',
                day: 'numeric'
            });
            document.getElementById('messages').innerHTML += generateBlock(pushMessage, image, senderName,
                time, true);

            // messages div scroll to bottom
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        });

    // messages div scroll to bottom
    messagesDiv.scrollTop = messagesDiv.scrollHeight;
})

Worker to keep reverb running

how to setup supervisor? https://notes.sohag.pro/supervisor-setup-for-laravel-application

Worker conf:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/laravel/artisan reverb:start
user=www-data
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/html/laravel/storage/logs/reverb.log
stopwaitsecs=3600

Nginx conf with support WSS

server {
        root /var/www/html/laravel/public;

        index index.php;
        server_name example.com;

        add_header X-Frame-Options "SAMEORIGIN";
        add_header X-XSS-Protection "1; mode=block";
        add_header X-Content-Type-Options "nosniff";


        charset utf-8;

        location / {
                try_files $uri $uri/ /index.php?$query_string;
            }

            location = /favicon.ico { access_log off; log_not_found off; }
            location = /robots.txt  { access_log off; log_not_found off; }

            error_page 404 /index.php;

            location ~ \.php$ {
                fastcgi_pass unix:/run/php/php8.2-fpm.sock;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
                include fastcgi_params;
            }

    location /app {
            proxy_http_version 1.1;
            proxy_set_header Host $http_host;
            proxy_set_header Scheme $scheme;
            proxy_set_header SERVER_PORT $server_port;
            proxy_set_header REMOTE_ADDR $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";

            proxy_pass http://0.0.0.0:8080;
        }

        location ~ /\.(?!well-known).* {
            deny all;
        }
}