Install Reverb in laravel
Documentation : https://laravel.com/docs/reverb
// Laravel 11 php artisan install:broadcasting
Install laravel echo in frontend with PusherJS
Documentation: https://laravel.com/docs/11.x/broadcasting#client-side-installation
npm install --save-dev laravel-echo pusher-js
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;
}
}